/*************************************************************************** * Copyright (C) 2008-2020 by Oliver Bock * * oliver.bock[AT]aei.mpg.de * * * * This file is part of Einstein@Home. * * * * Einstein@Home 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, version 2 of the License. * * * * Einstein@Home 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 Einstein@Home. If not, see <http://www.gnu.org/licenses/>. * * * ***************************************************************************/ #include "WindowManager.h" WindowManager::WindowManager() { m_ScreensaverMode = false; m_BoincAdapter = new BOINCClientAdapter(""); } WindowManager::~WindowManager() { delete m_BoincAdapter; } bool WindowManager::initialize(const int width, const int height, const int frameRate) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { cerr << "Window system could not be initalized: " << SDL_GetError() << endl; return false; } atexit(SDL_Quit); // retrieve current video settings (of the primary display) SDL_DisplayMode mode; if (!SDL_GetDesktopDisplayMode(0, &mode)) { m_DesktopWidth = mode.w; m_DesktopHeight = mode.h; m_DesktopBitsPerPixel = SDL_BITSPERPIXEL(mode.format); } // get initial non-fullscreen resolution and frame rate from project preferences m_BoincAdapter->initialize(); int preferredWidth = m_BoincAdapter->graphicsWindowWidth(); int preferredHeight = m_BoincAdapter->graphicsWindowHeight(); int preferredFrameRate = m_BoincAdapter->graphicsFrameRate(); // override optional default values if preferred values are set m_WindowedWidth = preferredWidth != 0 ? preferredWidth : width; m_WindowedHeight = preferredHeight != 0 ? preferredHeight : height; m_RenderEventInterval = 1000.0f / (preferredFrameRate != 0 ? preferredFrameRate : frameRate); /* * SDL_ASYNCBLIT - Surface benutzt asynchrone Blits, wenn möglich * SDL_ANYFORMAT - Erlaubt jedes Pixel-Format (nur beim display-surface) * SDL_FULLSCREEN - Surface im Full-Screen-Mode initialisieren (nur display-surface) * SDL_OPENGL - Surface nutzt OpenGL (nur display-surface) * SDL_RESIZABLE - Surfacefenster ist veränderbar (nur display-Surface) * SDL_HWACCEL- Surface blit nutzt Hardwarebeschleunigung * SDL_SRCCOLORKEY - Surface nutzt colorkey blitting * SDL_RLEACCEL - Colorkey blitting ist durch RLE beschleunigt * SDL_SRCALPHA - Surface blit nutzt alpha blending * SDL_PREALLOC - Surface nutzt vorher allokierten Speicher */ // set common video flags m_VideoModeFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; // enable HDPI (retina) support // m_VideoModeFlags |= SDL_WINDOW_ALLOW_HIGHDPI; // check fullscreen video mode m_FullscreenModeAvailable = true; // Uint32 bitPerPixel = SDL_VideoModeOK( // m_DesktopWidth, // m_DesktopHeight, // m_DesktopBitsPerPixel, // m_VideoModeFlags | SDL_WINDOW_FULLSCREEN); // // if(!bitPerPixel) { // cerr << "Fullscreen video mode not supported: " << SDL_GetError() << endl; // m_FullscreenModeAvailable = false; // } // // check initial windowed video mode m_WindowedModeAvailable = true; // bitPerPixel = SDL_VideoModeOK( // m_WindowedWidth, // m_WindowedHeight, // m_DesktopBitsPerPixel, // m_VideoModeFlags | SDL_WINDOW_RESIZABLE); // // if(!bitPerPixel) { // cerr << "Windowed video mode not supported: " << SDL_GetError() << endl; // m_WindowedModeAvailable = false; // } // // // both checks failed // if(!(m_FullscreenModeAvailable || m_WindowedModeAvailable)) { // cerr << "No suitable video mode available!"<< endl; // return false; // } // minimum requirements SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetSwapInterval(1); if(m_BoincAdapter->graphicsQualitySetting() == BOINCClientAdapter::HighGraphicsQualitySetting) { // enable opt-in quality feature FSAA (4x) SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); } // we always start in windowed mode (starting in fullscreen fails with high CPU load!) m_CurrentWidth = m_WindowedWidth; m_CurrentHeight = m_WindowedHeight; // finally, get window m_Window = SDL_CreateWindow( "Einstein@Home", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_CurrentWidth, m_CurrentHeight, m_VideoModeFlags); if (m_Window == NULL) { cerr << "Could not acquire rendering surface (" << SDL_GetError() << "): will try fallback..." << endl; } // check if we got acceleration int accelerated = 0; if(SDL_GL_GetAttribute(SDL_GL_ACCELERATED_VISUAL, &accelerated) == -1) { cerr << "Could not ensure accelerated rendering surface. Assuming no acceleration..." << endl; } if (m_Window == NULL || !accelerated) { cerr << "Disabling high quality features..." << endl; // disable features that demand acceleration SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); // TODO: we should override m_BoincAdapter->graphicsQualitySetting() to medium or low! // note, requires to extend starsphere's constructor (uses its own BOINCClientAdapter!) // reset window m_Window = SDL_CreateWindow( "Einstein@Home", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_CurrentWidth, m_CurrentHeight, m_VideoModeFlags); if (m_Window == NULL) { cerr << "Could not acquire window (" << SDL_GetError() << "): giving up!" << endl; return false; } } // finally create OpenGL context m_GLContext = SDL_GL_CreateContext(m_Window); if (m_GLContext == NULL) { cerr << "Could not acquire OpenGL context (" << SDL_GetError() << "): giving up!" << endl; return false; } return true; } void WindowManager::eventLoop() { // be sure there's at least one observer! assert(eventObservers.size() > 0); // set two main timers (interval in ms) SDL_AddTimer(m_RenderEventInterval, &timerCallbackRenderEvent, NULL); SDL_AddTimer(1000, &timerCallbackBOINCUpdateEvent, NULL); // events we don't ignore, hence use //SDL_EventState(SDL_QUIT, SDL_IGNORE); //SDL_EventState(SDL_KEYDOWN, SDL_IGNORE); //SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); //SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_IGNORE); //SDL_EventState(SDL_WINDOWEVENT_SIZE_CHANGED, SDL_IGNORE); //SDL_EventState(SDL_USEREVENT, SDL_IGNORE); // events we ignore SDL_EventState(SDL_KEYUP, SDL_IGNORE); SDL_EventState(SDL_JOYAXISMOTION, SDL_IGNORE); SDL_EventState(SDL_JOYBALLMOTION, SDL_IGNORE); SDL_EventState(SDL_JOYHATMOTION, SDL_IGNORE); SDL_EventState(SDL_JOYBUTTONDOWN, SDL_IGNORE); SDL_EventState(SDL_JOYBUTTONUP, SDL_IGNORE); SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE); SDL_Event event; while (SDL_WaitEvent(&event) ) { if (event.type == SDL_USEREVENT && event.user.code == RenderEvent) { #ifdef DEBUG_VALGRIND // stop after i iterations when running valgrinded static int i = 0; if(i < 500) { i++; #endif // notify our observers (currently exactly one, hence front()) eventObservers.front()->render(dtime()); SDL_GL_SwapWindow(m_Window); #ifdef DEBUG_VALGRIND } else { if (m_Window) { SDL_GL_DeleteContext(m_GLContext); SDL_DestroyWindow(m_Window); } break; } #endif } else if (event.type == SDL_USEREVENT && event.user.code == BOINCUpdateEvent) { // notify observers (currently exactly one, hence front()) to fetch a BOINC update eventObservers.front()->refreshBOINCInformation(); } else if (m_ScreensaverMode && (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN)) { // we're in screensaver mode so exit on user input SDL_GL_DeleteContext(m_GLContext); SDL_DestroyWindow(m_Window); SDL_Quit(); } else if (event.motion.state & (SDL_BUTTON(1) | SDL_BUTTON(3)) && event.type == SDL_MOUSEMOTION) { if (event.motion.state & SDL_BUTTON(1)) { // notify our observers (currently exactly one, hence front()) eventObservers.front()->mouseMoveEvent( event.motion.xrel, event.motion.yrel, AbstractGraphicsEngine::MouseButtonLeft); } else if (event.motion.state & SDL_BUTTON(3)) { // notify our observers (currently exactly one, hence front()) eventObservers.front()->mouseMoveEvent( event.motion.xrel, event.motion.yrel, AbstractGraphicsEngine::MouseButtonRight); } } else if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { m_CurrentWidth = m_WindowedWidth = event.window.data1; m_CurrentHeight = m_WindowedHeight = event.window.data2; // notify our observers (currently exactly one, hence front()) // (windoze needs to be reinitialized instead of just resized, oh well) /// \todo Can we determine the host OS? On X11 a resize() is sufficient! eventObservers.front()->initialize(m_CurrentWidth, m_CurrentHeight, 0, true); } else if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) { SDL_GL_DeleteContext(m_GLContext); SDL_DestroyWindow(m_Window); SDL_Quit(); break; } else if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { // notify our observers (currently exactly one, hence front()) case SDLK_s: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyS); break; case SDLK_c: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyC); break; case SDLK_o: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyO); break; case SDLK_x: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyX); break; case SDLK_p: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyP); break; case SDLK_r: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyR); break; case SDLK_g: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyG); break; case SDLK_a: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyA); break; case SDLK_i: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyI); break; case SDLK_l: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyL); break; case SDLK_m: eventObservers.front()->keyboardPressEvent(AbstractGraphicsEngine::KeyM); break; case SDLK_RETURN: toggleFullscreen(); default: break; } } } } void WindowManager::registerEventObserver(AbstractGraphicsEngine *engine) { // right now we're only accepting/using ONE observer eventObservers.assign(1, engine); } void WindowManager::unregisterEventObserver(AbstractGraphicsEngine *engine) { eventObservers.remove(engine); } Uint32 WindowManager::timerCallbackRenderEvent(Uint32 interval, void *param) { SDL_Event event; SDL_UserEvent userevent; userevent.type = SDL_USEREVENT; userevent.code = RenderEvent; userevent.data1 = NULL; userevent.data2 = NULL; event.type = SDL_USEREVENT; event.user = userevent; SDL_PushEvent(&event); return interval; } Uint32 WindowManager::timerCallbackBOINCUpdateEvent(Uint32 interval, void *param) { SDL_Event event; SDL_UserEvent userevent; userevent.type = SDL_USEREVENT; userevent.code = BOINCUpdateEvent; userevent.data1 = NULL; userevent.data2 = NULL; event.type = SDL_USEREVENT; event.user = userevent; SDL_PushEvent(&event); return interval; } int WindowManager::windowWidth() const { return m_CurrentWidth; } int WindowManager::windowHeight() const { return m_CurrentHeight; } void WindowManager::setWindowCaption(const string caption) const { SDL_SetWindowTitle(m_Window, caption.c_str()); } void WindowManager::setWindowIcon(const string filename) const { if (filename.length() > 0) { SDL_SetWindowIcon(m_Window, SDL_LoadBMP(filename.c_str())); } } void WindowManager::setWindowIcon(const unsigned char *data, const int size) const { // prepare data buffer structure SDL_RWops *buffer = SDL_RWFromMem((void*) data, size); if(buffer != NULL) { // load BMP from prepared data buffer SDL_Surface *surface = SDL_LoadBMP_RW(buffer, 1); if(surface != NULL) { // set window icon SDL_SetWindowIcon(m_Window, surface); SDL_FreeSurface(surface); } else { cerr << "Could not create window icon surface: " << SDL_GetError() << endl; } } else { cerr << "Could not prepare window icon data: " << SDL_GetError() << endl; } } void WindowManager::toggleFullscreen() { if(m_WindowedModeAvailable && (m_VideoModeFlags & SDL_WINDOW_FULLSCREEN)) { // restore windowed mode SDL_SetWindowFullscreen(m_Window, 0); // show cursor SDL_ShowCursor(SDL_ENABLE); // store state m_VideoModeFlags &= ~SDL_WINDOW_FULLSCREEN; } else if(m_FullscreenModeAvailable && !(m_VideoModeFlags & SDL_WINDOW_FULLSCREEN)) { // enable fullscreen mode // (not using real SDL_WINDOW_FULLSCREEN which triggers a SDL_MOUSEMOTION event) SDL_SetWindowFullscreen(m_Window, SDL_WINDOW_FULLSCREEN_DESKTOP); // hide cursor SDL_ShowCursor(SDL_DISABLE); // store state m_VideoModeFlags |= SDL_WINDOW_FULLSCREEN; } // notify our observers (currently exactly one, hence front()) // (windoze needs to be reinitialized instead of just resized, oh well) /// \todo Can we determine the host OS? On X11 a resize() is sufficient! eventObservers.front()->initialize(m_CurrentWidth, m_CurrentHeight, 0, true); } void WindowManager::setScreensaverMode(const bool enabled) { m_ScreensaverMode = enabled; }