boinc/api/x_opengl.C

425 lines
13 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include "x_opengl.h"
#include "app_ipc.h"
#include "util.h"
#include "filesys.h"
#include "boinc_gl.h"
#include "graphics_api.h"
#include "graphics_impl.h"
#define BOINC_WINDOW_CLASS_NAME "BOINC_app"
#define TIMER_INTERVAL_MSEC 30
static bool visible = true;
static int current_graphics_mode = MODE_HIDE_GRAPHICS;
static int acked_graphics_mode;
static int xpos = 100, ypos = 100;
static int clicked_button;
static int win=0;
static bool glut_is_initialized = false;
static jmp_buf jbuf; // longjump/setjump for exit/signal handler
static struct sigaction original_signal_handler; // store previous ABRT signal-handler
static void set_mode(int mode);
static void restart(void);
static void boinc_glut_init(void);
static APP_INIT_DATA aid;
extern pthread_t graphics_thread; // thread info
extern pthread_t worker_thread;
void app_debug_msg (char *fmt, ...);
// This callback is invoked when a user presses a key.
//
void keyboardD(unsigned char key, int x, int y) {
if (current_graphics_mode == MODE_FULLSCREEN) {
set_mode(MODE_HIDE_GRAPHICS);
}
}
void keyboardU(unsigned char key, int x, int y) {
if (current_graphics_mode == MODE_FULLSCREEN) {
set_mode(MODE_HIDE_GRAPHICS);
}
}
void mouse_click(int button, int state, int x, int y){
clicked_button = button;
if (current_graphics_mode == MODE_FULLSCREEN) {
set_mode(MODE_HIDE_GRAPHICS);
} else {
if (state) {
boinc_app_mouse_button(x, y, button, false);
} else {
boinc_app_mouse_button(x, y, button, true);
}
}
}
void mouse_click_move(int x, int y){
if (current_graphics_mode == MODE_FULLSCREEN){
set_mode(MODE_HIDE_GRAPHICS);
} else if (clicked_button == 2){
boinc_app_mouse_move(x, y, false, false, true);
} else if (clicked_button == 1){
boinc_app_mouse_move(x, y, false, true, false);
} else if (clicked_button == 0){
boinc_app_mouse_move(x, y, true, false, false);
} else{
boinc_app_mouse_move(x, y, false, false, false);
}
}
static void maybe_render() {
int width, height;
if (visible && (current_graphics_mode != MODE_HIDE_GRAPHICS)) {
width = glutGet(GLUT_WINDOW_WIDTH);
height = glutGet(GLUT_WINDOW_HEIGHT);
if (throttled_app_render(width, height, dtime())) {
glutSwapBuffers();
}
}
}
static void make_new_window(int mode)
{
if ( (mode != MODE_WINDOW) && (mode != MODE_FULLSCREEN) ) // nothing to be done here
return;
if ( ! glut_is_initialized ) //initialize glut if necessary
boinc_glut_init();
app_debug_msg("make_new_window(): now calling glutCreateWindow(%s)...\n", aid.app_name);
win = glutCreateWindow(aid.app_name);
app_debug_msg("glutCreateWindow() succeeded. win = %d\n", win);
// installing callbacks for the current window
glutReshapeFunc(app_graphics_resize);
glutKeyboardFunc(keyboardD);
glutKeyboardUpFunc(keyboardU);
glutMouseFunc(mouse_click);
glutMotionFunc(mouse_click_move);
glutDisplayFunc(maybe_render);
glEnable(GL_DEPTH_TEST);
app_graphics_init();
if (mode == MODE_FULLSCREEN)
glutFullScreen();
return;
} // make_new_window()
// initialized glut and screensaver-graphics
// this should only called once, even if restarted by user-exit
static void
boinc_glut_init(void)
{
char* args[2] = {"screensaver", NULL};
int one=1;
app_debug_msg("Calling glutInit()... \n");
glutInit (&one, args);
app_debug_msg("...survived glutInit(). \n");
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowPosition(xpos, ypos);
glutInitWindowSize(600, 400);
g_bmsp->boinc_get_init_data_hook(aid);
if (!strlen(aid.app_name))
strcpy(aid.app_name, "BOINC Application");
glut_is_initialized = true;
return;
} // boinc_glut_init()
// Destroy current window if any
void KillWindow() {
if (win) {
int oldwin = win;
win = 0; // set this to 0 FIRST to avoid recursion if the following call fails.
glutDestroyWindow(oldwin);
}
}
void set_mode(int mode) {
if (mode == current_graphics_mode) return;
app_debug_msg("set_mode(%d): current_mode = %d. Calling KillWindow(): win = %d\n", mode, current_graphics_mode, win);
KillWindow();
app_debug_msg("...KillWindow() survived.\n");
current_graphics_mode = mode;
if (mode != MODE_HIDE_GRAPHICS) {
app_debug_msg("set_mode(): Calling make_new_window(%d)\n", mode);
make_new_window(mode);
app_debug_msg("...make_new_window() survived.\n");
}
return;
} // set_mode()
static void wait_for_initial_message() {
(*g_bmsp->app_client_shmp)->shm->graphics_reply.send_msg(
xml_graphics_modes[MODE_HIDE_GRAPHICS]
);
acked_graphics_mode = MODE_HIDE_GRAPHICS;
while (1) {
if ((*g_bmsp->app_client_shmp)->shm->graphics_request.has_msg()) {
break;
}
sleep(1);
}
return;
} // wait_for_initial_message()
static void timer_handler(int) {
char buf[MSG_CHANNEL_SIZE];
GRAPHICS_MSG m;
int new_mode;
if (*g_bmsp->app_client_shmp) {
if ((*g_bmsp->app_client_shmp)->shm->graphics_request.get_msg(buf)) {
(*g_bmsp->app_client_shmp)->decode_graphics_msg(buf, m);
switch (m.mode) {
case MODE_REREAD_PREFS:
//only reread graphics prefs if we have a window open
//
switch (current_graphics_mode){
case MODE_WINDOW:
case MODE_FULLSCREEN:
app_graphics_reread_prefs();
break;
}
break;
case MODE_HIDE_GRAPHICS:
case MODE_WINDOW:
case MODE_FULLSCREEN:
case MODE_BLANKSCREEN:
set_mode(m.mode);
break;
}
}
if (acked_graphics_mode != current_graphics_mode) {
bool sent = (*g_bmsp->app_client_shmp)->shm->graphics_reply.send_msg(
xml_graphics_modes[current_graphics_mode]
);
if (sent) acked_graphics_mode = current_graphics_mode;
}
}
maybe_render();
glutTimerFunc(TIMER_INTERVAL_MSEC, timer_handler, 0);
return;
} // timer_handler()
// exit handler: really only meant to handle exits() called by GLUT...
void restart()
{
// don't do anything except when exit is called from the graphics-thread
if ( !pthread_equal(pthread_self(), graphics_thread) )
app_debug_msg ("exit() was called from worker-thread\n");
else
{
app_debug_msg ("exit() called from graphics-thread.\n");
// we're standalone, a glut-window was open: user must have pressed 'close', so we exit
if (boinc_is_standalone() && glut_is_initialized && win)
{
app_debug_msg("Open glut-window found... means we're exiting now.\n");
if ( boinc_delete_file(LOCKFILE) != 0)
perror ("Failed to remove lockfile..\n");
return;
}
// re-install the exit-handler to catch glut's notorious exits()...
atexit(restart);
// jump back to entry-point in xwin_graphics_event_loop();
app_debug_msg( "Jumping back to event_loop.\n");
longjmp(jbuf, 1);
} // if in graphics-thread
return;
} // restart()
// deal with ABORT's, typically raised by GLUT
// If we are in the graphics thread, we just return back
// into xwin_graphics_event_loop. If we are in the main thread, then
// restore the old signal handler and raise SIGABORT.
void restart_sig(int signal_number)
{
if ( pthread_equal(pthread_self(), graphics_thread) )
{
// alternative approach is for the signal hander to call exit().
fprintf(stderr, "Caught SIGABRT in graphics thread\n");
app_debug_msg ("Caught SIGABRT in graphics thread. Jumping back to xwin_graphics_event_loop()...\n");
// jump back to entry-point in xwin_graphics_event_loop()
longjmp(jbuf, 1);
} // if ABRT raised in graphics-thread
else
{
// In non-graphics thread: use original signal handler
fprintf(stderr, "Caught SIGABRT in non-graphics thread\n");
app_debug_msg ("Caught SIGABRT in non-graphics thread. Trying to call previous ABRT-handler...\n");
if (sigaction(SIGABRT, &original_signal_handler, NULL))
{
perror("Unable to restore SIGABRT signal handler in non-graphics thread\n");
// what to do? call abort(3)?? call exit(nonzero)???
// Here we just return.
}
else
{
// we could conceivably try to call the old signal handler
// directly. But this means checking how its flags/masks
// are set, making sure it's not NULL (for no action) and so
// on. This seems simpler and more robust. On the other
// hand it may take some time for the signal to be
// delivered, and during that time, what's going to be
// executing?
raise(SIGABRT);
}
} // if ABRT raised in non-graphics thread
return;
} // restart_sig()
// graphics thread main-function
// NOTE: this should only be called *ONCE* by an application
void xwin_graphics_event_loop()
{
int restarted;
app_debug_msg ("Direct call to xwin_graphics_event_loop()\n");
struct sigaction sa;
sa.sa_handler = restart_sig;
sa.sa_flags = SA_RESTART;
// install signal handler to catch abort() from GLUT. Note that
// signal handlers are global to ALL threads, so the signal
// handler that we record here in &original_signal_handler is the
// previous one that applied to BOTH threads
if (sigaction(SIGABRT, &sa, &original_signal_handler)) {
perror("unable to install signal handler to catch SIGABORT");
}
// handle glut's notorious exits()..
atexit(restart);
// THIS IS THE re-entry point at exit or ABORT in the graphics-thread
restarted = setjmp(jbuf);
if (restarted)
app_debug_msg("xwin_graphics_event_loop() restarted at re-entry point\n");
if ( boinc_is_standalone() )
{
if (restarted && !win) // now window was yet opened: just go to sleep to avoid more trouble...
{
app_debug_msg("Graphics exited before glut-window opened... continuing without graphics\n");
while(1) sleep(1);
return; // should never arrive here
} // if restarted & no open windows
if ( !restarted ) // first time through here
{
// open the graphics-window
set_mode(MODE_WINDOW);
glutTimerFunc(TIMER_INTERVAL_MSEC, timer_handler, 0);
}
} // if standalone-mode
else // under BOINC: start with graphics hidden and wait for client to tell us otherwise
{
// Mac's GLUT is sufficiently "broken" that I simply don't see a way of restarting
// a GLUT-window after user exited once (e.g. using M-Q, or "Quit einstein" in menu).
// glut will segfault or similar at the attempt, so we rather don't try this in the first place.
// ==> If user exited glut-window once on mac, we put this thread to sleep
// (until somebody has a better idea / understanding of Mac's glut
#ifdef __APPLE_CC__
if (restarted)
{
app_debug_msg("Sorry. I don't know a way of restarting glut on the Mac. Putting graphics to sleep now.\n");
while(1) sleep(1);
return;
}
#endif
// on Linux on the other hand, glut seems to be happily restartable after exit was caught,
// so we simply continue as if it was the first time...: i.e. wait for client-message, then do something.
app_debug_msg ("Switching graphics-mode to MODE_HIDE_GRAPHICS\n");
set_mode(MODE_HIDE_GRAPHICS);
while ( current_graphics_mode == MODE_HIDE_GRAPHICS )
{
app_debug_msg ("Graphics-thread now waiting for client-message...\n");
wait_for_initial_message();
app_debug_msg ("got a graphics-message from client... \n");
timer_handler(0);
}
} // ! boinc_is_standalone()
// ok we should be ready & initialized by now to call glutMainLoop()
app_debug_msg ("now calling glutMainLoop()...\n");
glutMainLoop();
app_debug_msg("...glutMainLoop() returned!! This should never happen...\n");
return;
} // xwin_graphics_event_loop()
/* RP's little APP-debug output function: */
void
app_debug_msg (char *fmt, ...)
{
#ifndef _DEBUG
return;
#else
va_list args;
char buffer[5000+1];
static char *boinc_slotdir = NULL;
va_start (args, fmt);
if (boinc_slotdir == NULL)
{
char *tmp, *ptr;
if ( (tmp = getcwd(NULL, 0)) == NULL)
{
perror ("failed to get working directory using getcwd()");
boinc_slotdir = (char*)calloc(1, 20);
strcpy( boinc_slotdir, "[unknown]");
}
else
{
if ( (ptr = strrchr(tmp, '/')) == NULL)
ptr = tmp;
else
ptr ++;
boinc_slotdir = (char*)calloc(1, strlen(ptr)+1);
strcpy(boinc_slotdir, ptr);
free (tmp);
} /* got cwd */
}
vsnprintf (buffer, 5000, fmt, args);
fprintf (stdout, "APP '%s' DEBUG: ", boinc_slotdir);
fprintf (stdout, buffer);
fflush (stdout);
va_end (args);
#endif // defined _DEBUG
} // app_debug_msg()
const char *BOINC_RCSID_c457a14644 = "$Id$";