// Filename: androidGraphicsWindow.cxx // Created by: rdb (11Jan13) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "androidGraphicsWindow.h" #include "androidGraphicsStateGuardian.h" #include "config_androiddisplay.h" #include "androidGraphicsPipe.h" #include "graphicsPipe.h" #include "keyboardButton.h" #include "mouseButton.h" #include "clockObject.h" #include "pStatTimer.h" #include "textEncoder.h" #include "throw_event.h" #include "nativeWindowHandle.h" #include "android_native_app_glue.h" #include #include extern struct android_app* panda_android_app; TypeHandle AndroidGraphicsWindow::_type_handle; //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// AndroidGraphicsWindow:: AndroidGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, const string &name, const FrameBufferProperties &fb_prop, const WindowProperties &win_prop, int flags, GraphicsStateGuardian *gsg, GraphicsOutput *host) : GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) { AndroidGraphicsPipe *android_pipe; DCAST_INTO_V(android_pipe, _pipe); _egl_display = android_pipe->_egl_display; _egl_surface = 0; _app = panda_android_app; GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::Destructor // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// AndroidGraphicsWindow:: ~AndroidGraphicsWindow() { destroy_surface(); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::begin_frame // Access: Public, Virtual // Description: This function will be called within the draw thread // before beginning rendering for a given frame. It // should do whatever setup is required, and return true // if the frame should be rendered, or false if it // should be skipped. //////////////////////////////////////////////////////////////////// bool AndroidGraphicsWindow:: begin_frame(FrameMode mode, Thread *current_thread) { PStatTimer timer(_make_current_pcollector, current_thread); begin_frame_spam(mode); if (_gsg == (GraphicsStateGuardian *)NULL) { return false; } //XXX not open yet. if (_egl_surface == EGL_NO_SURFACE) { return false; } AndroidGraphicsStateGuardian *androidgsg; DCAST_INTO_R(androidgsg, _gsg, false); { if (eglGetCurrentDisplay() == _egl_display && eglGetCurrentSurface(EGL_READ) == _egl_surface && eglGetCurrentSurface(EGL_DRAW) == _egl_surface && eglGetCurrentContext() == androidgsg->_context) { // No need to make the context current again. Short-circuit // this possibly-expensive call. } else { // Need to set the context. if (!eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, androidgsg->_context)) { androiddisplay_cat.error() << "Failed to call eglMakeCurrent: " << get_egl_error_string(eglGetError()) << "\n"; } } } // Now that we have made the context current to a window, we can // reset the GSG state if this is the first time it has been used. // (We can't just call reset() when we construct the GSG, because // reset() requires having a current context.) androidgsg->reset_if_new(); if (mode == FM_render) { // begin_render_texture(); clear_cube_map_selection(); } _gsg->set_current_properties(&get_fb_properties()); return _gsg->begin_frame(current_thread); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::end_frame // Access: Public, Virtual // Description: This function will be called within the draw thread // after rendering is completed for a given frame. It // should do whatever finalization is required. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: end_frame(FrameMode mode, Thread *current_thread) { end_frame_spam(mode); nassertv(_gsg != (GraphicsStateGuardian *)NULL); if (mode == FM_render) { // end_render_texture(); copy_to_textures(); } _gsg->end_frame(current_thread); if (mode == FM_render) { trigger_flip(); clear_cube_map_selection(); } } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::end_flip // Access: Public, Virtual // Description: This function will be called within the draw thread // after begin_flip() has been called on all windows, to // finish the exchange of the front and back buffers. // // This should cause the window to wait for the flip, if // necessary. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: end_flip() { if (_gsg != (GraphicsStateGuardian *)NULL && _flip_ready) { // It doesn't appear to be necessary to ensure the graphics // context is current before flipping the windows, and insisting // on doing so can be a significant performance hit. //make_current(); if (_egl_surface != EGL_NO_SURFACE) { eglSwapBuffers(_egl_display, _egl_surface); } } GraphicsWindow::end_flip(); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::process_events // Access: Public, Virtual // Description: Do whatever processing is necessary to ensure that // the window responds to user events. Also, honor any // requests recently made via request_properties() // // This function is called only within the window // thread. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: process_events() { GraphicsWindow::process_events(); // Read all pending events. int looper_id; int events; struct android_poll_source* source; // Loop until all events are read. while ((looper_id = ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { source->process(_app, source); } } } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::set_properties_now // Access: Public, Virtual // Description: Applies the requested set of properties to the // window, if possible, for instance to request a change // in size or minimization status. // // The window properties are applied immediately, rather // than waiting until the next frame. This implies that // this method may *only* be called from within the // window thread. // // The return value is true if the properties are set, // false if they are ignored. This is mainly useful for // derived classes to implement extensions to this // function. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: set_properties_now(WindowProperties &properties) { if (_pipe == (GraphicsPipe *)NULL) { // If the pipe is null, we're probably closing down. GraphicsWindow::set_properties_now(properties); return; } GraphicsWindow::set_properties_now(properties); if (!properties.is_any_specified()) { // The base class has already handled this case. return; } // There's not really much we can change on Android. if (properties.has_fullscreen()) { uint32_t add_flags = 0; uint32_t del_flags = 0; if (_properties.get_fullscreen()) { add_flags |= AWINDOW_FLAG_FULLSCREEN; } else { del_flags |= AWINDOW_FLAG_FULLSCREEN; } ANativeActivity_setWindowFlags(_app->activity, add_flags, del_flags); _properties.set_fullscreen(properties.get_fullscreen()); properties.clear_fullscreen(); } } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::close_window // Access: Protected, Virtual // Description: Closes the window right now. Called from the window // thread. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: close_window() { destroy_surface(); if (_gsg != (GraphicsStateGuardian *)NULL) { _gsg.clear(); } GraphicsWindow::close_window(); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::open_window // Access: Protected, Virtual // Description: Opens the window right now. Called from the window // thread. Returns true if the window is successfully // opened, or false if there was a problem. //////////////////////////////////////////////////////////////////// bool AndroidGraphicsWindow:: open_window() { // GSG Creation/Initialization AndroidGraphicsStateGuardian *androidgsg; if (_gsg == 0) { // There is no old gsg. Create a new one. androidgsg = new AndroidGraphicsStateGuardian(_engine, _pipe, NULL); androidgsg->choose_pixel_format(_fb_properties, false, false); _gsg = androidgsg; } else { // If the old gsg has the wrong pixel format, create a // new one that shares with the old gsg. DCAST_INTO_R(androidgsg, _gsg, false); if (!androidgsg->get_fb_properties().subsumes(_fb_properties)) { androidgsg = new AndroidGraphicsStateGuardian(_engine, _pipe, androidgsg); androidgsg->choose_pixel_format(_fb_properties, false, false); _gsg = androidgsg; } } // Register the callbacks assert(_app != NULL); _app->userData = this; _app->onAppCmd = handle_command; _app->onInputEvent = handle_input_event; // Wait until Android has opened the window. while (_app->window == NULL) { process_events(); } // create_surface should have been called by now. if (_egl_surface == EGL_NO_SURFACE) { return false; } // Set some other properties. _properties.set_origin(0, 0); _properties.set_cursor_hidden(true); _properties.set_undecorated(true); if (!androidgsg->get_fb_properties().verify_hardware_software (_fb_properties, androidgsg->get_gl_renderer())) { close_window(); return false; } //_fb_properties = androidgsg->get_fb_properties(); androiddisplay_cat.error() << "open_window done\n"; return true; } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::destroy_surface // Access: Protected, Virtual // Description: Terminates the EGL surface. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: destroy_surface() { if (_egl_surface != EGL_NO_SURFACE) { if (!eglDestroySurface(_egl_display, _egl_surface)) { androiddisplay_cat.error() << "Failed to destroy surface: " << get_egl_error_string(eglGetError()) << "\n"; } _egl_surface = EGL_NO_SURFACE; } // Destroy the current context. if (_gsg != (GraphicsStateGuardian *)NULL) { AndroidGraphicsStateGuardian *androidgsg; DCAST_INTO_V(androidgsg, _gsg); androidgsg->destroy_context(); } } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::create_surface // Access: Protected, Virtual // Description: Creates the EGL surface. //////////////////////////////////////////////////////////////////// bool AndroidGraphicsWindow:: create_surface() { AndroidGraphicsStateGuardian *androidgsg; DCAST_INTO_R(androidgsg, _gsg, false); // Reconfigure the window buffers to match that of our framebuffer config. ANativeWindow_setBuffersGeometry(_app->window, 0, 0, androidgsg->_format); // Set any window flags uint32_t add_flags = 0; uint32_t del_flags = 0; if (_properties.get_fullscreen()) { add_flags |= AWINDOW_FLAG_FULLSCREEN; } else { del_flags |= AWINDOW_FLAG_FULLSCREEN; } ANativeActivity_setWindowFlags(_app->activity, add_flags, del_flags); // Create the EGL surface. _egl_surface = eglCreateWindowSurface(_egl_display, androidgsg->_fbconfig, _app->window, NULL); if (eglGetError() != EGL_SUCCESS) { androiddisplay_cat.error() << "Failed to create window surface.\n"; return false; } // Create a context. if (androidgsg->_context == EGL_NO_CONTEXT) { androiddisplay_cat.error() << "creating context\n"; if (!androidgsg->create_context()) { return false; } } // Switch to our newly created context. if (!eglMakeCurrent(_egl_display, _egl_surface, _egl_surface, androidgsg->_context)) { androiddisplay_cat.error() << "Failed to call eglMakeCurrent: " << get_egl_error_string(eglGetError()) << "\n"; } // Query the size of the surface. //EGLint width, height; //eglQuerySurface(_egl_display, _egl_surface, EGL_WIDTH, &width); //eglQuerySurface(_egl_display, _egl_surface, EGL_HEIGHT, &height); androidgsg->reset_if_new(); if (!androidgsg->is_valid()) { close_window(); return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::handle_command // Access: Private, Static // Description: Android app sends a command from the main thread. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: handle_command(struct android_app *app, int32_t command) { AndroidGraphicsWindow* window = (AndroidGraphicsWindow*) app->userData; window->ns_handle_command(command); } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::ns_handle_command // Access: Private // Description: Android app sends a command from the main thread. //////////////////////////////////////////////////////////////////// void AndroidGraphicsWindow:: ns_handle_command(int32_t command) { WindowProperties properties; switch (command) { case APP_CMD_SAVE_STATE: // The system has asked us to save our current state. Do so. //engine->app->savedState = malloc(sizeof(struct saved_state)); //*((struct saved_state*)engine->app->savedState) = engine->state; //engine->app->savedStateSize = sizeof(struct saved_state); break; case APP_CMD_INIT_WINDOW: // The window is being shown, get it ready. if (_app->window != NULL) { create_surface(); properties.set_minimized(false); system_changed_properties(properties); } break; case APP_CMD_TERM_WINDOW: destroy_surface(); properties.set_minimized(true); system_changed_properties(properties); break; case APP_CMD_WINDOW_RESIZED: break; case APP_CMD_WINDOW_REDRAW_NEEDED: break; case APP_CMD_CONTENT_RECT_CHANGED: properties.set_origin(_app->contentRect.left, _app->contentRect.top); properties.set_size(_app->contentRect.right - _app->contentRect.left, _app->contentRect.bottom - _app->contentRect.top); system_changed_properties(properties); break; case APP_CMD_GAINED_FOCUS: properties.set_foreground(true); system_changed_properties(properties); break; case APP_CMD_LOST_FOCUS: properties.set_foreground(false); system_changed_properties(properties); break; case APP_CMD_DESTROY: close_window(); properties.set_open(false); system_changed_properties(properties); break; } } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::handle_input_event // Access: Private, Static // Description: Processes an input event. Returns 1 if the event // was handled, 0 otherwise. //////////////////////////////////////////////////////////////////// int32_t AndroidGraphicsWindow:: handle_input_event(struct android_app* app, AInputEvent *event) { AndroidGraphicsWindow* window = (AndroidGraphicsWindow*) app->userData; int32_t event_type = AInputEvent_getType(event); switch (event_type) { case AINPUT_EVENT_TYPE_KEY: return window->handle_key_event(event); case AINPUT_EVENT_TYPE_MOTION: return window->handle_motion_event(event); } return 0; } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::handle_keystroke // Access: Private // Description: Processes a key event. //////////////////////////////////////////////////////////////////// int32_t AndroidGraphicsWindow:: handle_key_event(const AInputEvent *event) { /* int32_t meta = AKeyEvent_getMetaState(event); if (meta | AMETA_ALT_ON) { _input_devices[0].button_down(KeyboardButton.alt()); } if (meta | AMETA_ALT_LEFT_ON) { _input_devices[0].button_down(KeyboardButton.lalt()); } if (meta | AMETA_ALT_RIGHT_ON) { _input_devices[0].button_down(KeyboardButton.ralt()); } if (meta | AMETA_SHIFT_ON) { _input_devices[0].button_down(KeyboardButton.shift()); } if (meta | AMETA_SHIFT_LEFT_ON) { _input_devices[0].button_down(KeyboardButton.lshift()); } if (meta | AMETA_SHIFT_RIGHT_ON) { _input_devices[0].button_down(KeyboardButton.rshift()); }*/ int32_t keycode = AKeyEvent_getKeyCode(event); ButtonHandle button = map_button(keycode); if (button == ButtonHandle::none()) { androiddisplay_cat.warning() << "Unknown keycode: " << keycode << "\n"; return 0; } // Is it an up or down event? int32_t action = AKeyEvent_getAction(event); if (action == AKEY_EVENT_ACTION_DOWN) { _input_devices[0].button_down(button); } else if (action == AKEY_EVENT_ACTION_UP) { _input_devices[0].button_up(button); } //TODO getRepeatCount, ACTION_MULTIPLE return 1; } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::handle_motion_event // Access: Private // Description: Processes a motion event. //////////////////////////////////////////////////////////////////// int32_t AndroidGraphicsWindow:: handle_motion_event(const AInputEvent *event) { int32_t action = AMotionEvent_getAction(event); action &= AMOTION_EVENT_ACTION_MASK; if (action == AMOTION_EVENT_ACTION_DOWN) { _input_devices[0].button_down(MouseButton::one()); } else if (action == AMOTION_EVENT_ACTION_UP) { _input_devices[0].button_up(MouseButton::one()); } float x = AMotionEvent_getX(event, 0) - _app->contentRect.left; float y = AMotionEvent_getY(event, 0) - _app->contentRect.top; _input_devices[0].set_pointer_in_window(x, y); return 1; } //////////////////////////////////////////////////////////////////// // Function: AndroidGraphicsWindow::map_button // Access: Private // Description: Given an Android keycode, returns an appropriate // ButtonHandle object, or ButtonHandle::none() if // a matching ButtonHandle does not exist. //////////////////////////////////////////////////////////////////// ButtonHandle AndroidGraphicsWindow:: map_button(int32_t keycode) { switch (keycode) { case AKEYCODE_SOFT_LEFT: case AKEYCODE_SOFT_RIGHT: case AKEYCODE_HOME: case AKEYCODE_BACK: case AKEYCODE_CALL: case AKEYCODE_ENDCALL: break; case AKEYCODE_0: return KeyboardButton::ascii_key('0'); case AKEYCODE_1: return KeyboardButton::ascii_key('1'); case AKEYCODE_2: return KeyboardButton::ascii_key('2'); case AKEYCODE_3: return KeyboardButton::ascii_key('3'); case AKEYCODE_4: return KeyboardButton::ascii_key('4'); case AKEYCODE_5: return KeyboardButton::ascii_key('5'); case AKEYCODE_6: return KeyboardButton::ascii_key('6'); case AKEYCODE_7: return KeyboardButton::ascii_key('7'); case AKEYCODE_8: return KeyboardButton::ascii_key('8'); case AKEYCODE_9: return KeyboardButton::ascii_key('9'); case AKEYCODE_STAR: return KeyboardButton::ascii_key('*'); case AKEYCODE_POUND: return KeyboardButton::ascii_key('#'); case AKEYCODE_DPAD_UP: return KeyboardButton::up(); case AKEYCODE_DPAD_DOWN: return KeyboardButton::down(); case AKEYCODE_DPAD_LEFT: return KeyboardButton::left(); case AKEYCODE_DPAD_RIGHT: return KeyboardButton::right(); case AKEYCODE_DPAD_CENTER: case AKEYCODE_VOLUME_UP: case AKEYCODE_VOLUME_DOWN: case AKEYCODE_POWER: case AKEYCODE_CAMERA: case AKEYCODE_CLEAR: break; case AKEYCODE_A: return KeyboardButton::ascii_key('a'); case AKEYCODE_B: return KeyboardButton::ascii_key('b'); case AKEYCODE_C: return KeyboardButton::ascii_key('c'); case AKEYCODE_D: return KeyboardButton::ascii_key('d'); case AKEYCODE_E: return KeyboardButton::ascii_key('e'); case AKEYCODE_F: return KeyboardButton::ascii_key('f'); case AKEYCODE_G: return KeyboardButton::ascii_key('g'); case AKEYCODE_H: return KeyboardButton::ascii_key('h'); case AKEYCODE_I: return KeyboardButton::ascii_key('i'); case AKEYCODE_J: return KeyboardButton::ascii_key('j'); case AKEYCODE_K: return KeyboardButton::ascii_key('k'); case AKEYCODE_L: return KeyboardButton::ascii_key('l'); case AKEYCODE_M: return KeyboardButton::ascii_key('m'); case AKEYCODE_N: return KeyboardButton::ascii_key('n'); case AKEYCODE_O: return KeyboardButton::ascii_key('o'); case AKEYCODE_P: return KeyboardButton::ascii_key('p'); case AKEYCODE_Q: return KeyboardButton::ascii_key('q'); case AKEYCODE_R: return KeyboardButton::ascii_key('r'); case AKEYCODE_S: return KeyboardButton::ascii_key('s'); case AKEYCODE_T: return KeyboardButton::ascii_key('t'); case AKEYCODE_U: return KeyboardButton::ascii_key('u'); case AKEYCODE_V: return KeyboardButton::ascii_key('v'); case AKEYCODE_W: return KeyboardButton::ascii_key('w'); case AKEYCODE_X: return KeyboardButton::ascii_key('x'); case AKEYCODE_Y: return KeyboardButton::ascii_key('y'); case AKEYCODE_Z: return KeyboardButton::ascii_key('z'); case AKEYCODE_COMMA: return KeyboardButton::ascii_key(','); case AKEYCODE_PERIOD: return KeyboardButton::ascii_key('.'); case AKEYCODE_ALT_LEFT: return KeyboardButton::lalt(); case AKEYCODE_ALT_RIGHT: return KeyboardButton::ralt(); case AKEYCODE_SHIFT_LEFT: return KeyboardButton::lshift(); case AKEYCODE_SHIFT_RIGHT: return KeyboardButton::rshift(); case AKEYCODE_TAB: return KeyboardButton::tab(); case AKEYCODE_SPACE: return KeyboardButton::space(); case AKEYCODE_SYM: case AKEYCODE_EXPLORER: case AKEYCODE_ENVELOPE: break; case AKEYCODE_ENTER: return KeyboardButton::enter(); case AKEYCODE_DEL: return KeyboardButton::del(); case AKEYCODE_GRAVE: return KeyboardButton::ascii_key('`'); case AKEYCODE_MINUS: return KeyboardButton::ascii_key('-'); case AKEYCODE_EQUALS: return KeyboardButton::ascii_key('='); case AKEYCODE_LEFT_BRACKET: return KeyboardButton::ascii_key('['); case AKEYCODE_RIGHT_BRACKET: return KeyboardButton::ascii_key(']'); case AKEYCODE_BACKSLASH: return KeyboardButton::ascii_key('\\'); case AKEYCODE_SEMICOLON: return KeyboardButton::ascii_key(';'); case AKEYCODE_APOSTROPHE: return KeyboardButton::ascii_key('\''); case AKEYCODE_SLASH: return KeyboardButton::ascii_key('/'); case AKEYCODE_AT: return KeyboardButton::ascii_key('@'); case AKEYCODE_NUM: case AKEYCODE_HEADSETHOOK: case AKEYCODE_FOCUS: break; case AKEYCODE_PLUS: return KeyboardButton::ascii_key('+'); case AKEYCODE_MENU: case AKEYCODE_NOTIFICATION: case AKEYCODE_SEARCH: case AKEYCODE_MEDIA_PLAY_PAUSE: case AKEYCODE_MEDIA_STOP: case AKEYCODE_MEDIA_NEXT: case AKEYCODE_MEDIA_PREVIOUS: case AKEYCODE_MEDIA_REWIND: case AKEYCODE_MEDIA_FAST_FORWARD: case AKEYCODE_MUTE: break; case AKEYCODE_PAGE_UP: return KeyboardButton::page_up(); case AKEYCODE_PAGE_DOWN: return KeyboardButton::page_down(); case AKEYCODE_PICTSYMBOLS: case AKEYCODE_SWITCH_CHARSET: case AKEYCODE_BUTTON_A: case AKEYCODE_BUTTON_B: case AKEYCODE_BUTTON_C: case AKEYCODE_BUTTON_X: case AKEYCODE_BUTTON_Y: case AKEYCODE_BUTTON_Z: case AKEYCODE_BUTTON_L1: case AKEYCODE_BUTTON_R1: case AKEYCODE_BUTTON_L2: case AKEYCODE_BUTTON_R2: case AKEYCODE_BUTTON_THUMBL: case AKEYCODE_BUTTON_THUMBR: case AKEYCODE_BUTTON_START: case AKEYCODE_BUTTON_SELECT: case AKEYCODE_BUTTON_MODE: default: break; } return ButtonHandle::none(); }