1 /* 2 * Copyright 2011 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 #include "SkOSWindow_SDL.h" 8 #include "SkCanvas.h" 9 10 #if defined(SK_BUILD_FOR_ANDROID) 11 #include <GLES/gl.h> 12 #elif defined(SK_BUILD_FOR_UNIX) 13 #include <GL/gl.h> 14 #elif defined(SK_BUILD_FOR_MAC) 15 #include <gl.h> 16 #endif 17 18 const int kInitialWindowWidth = 640; 19 const int kInitialWindowHeight = 480; 20 static SkOSWindow* gCurrentWindow; 21 22 static void report_sdl_error(const char* failure) { 23 const char* error = SDL_GetError(); 24 SkASSERT(error); // Called only to check SDL error. 25 SkDebugf("%s SDL Error: %s.\n", failure, error); 26 SDL_ClearError(); 27 } 28 SkOSWindow::SkOSWindow(void*) 29 : fWindow(nullptr) 30 , fGLContext(nullptr) 31 , fWindowMSAASampleCount(0) { 32 33 SkASSERT(!gCurrentWindow); 34 gCurrentWindow = this; 35 36 this->createWindow(0); 37 } 38 39 SkOSWindow::~SkOSWindow() { 40 this->destroyWindow(); 41 gCurrentWindow = nullptr; 42 } 43 44 SkOSWindow* SkOSWindow::GetInstanceForWindowID(Uint32 windowID) { 45 if (gCurrentWindow && 46 gCurrentWindow->fWindow && 47 SDL_GetWindowID(gCurrentWindow->fWindow) == windowID) { 48 return gCurrentWindow; 49 } 50 return nullptr; 51 } 52 53 void SkOSWindow::detach() { 54 if (fGLContext) { 55 SDL_GL_DeleteContext(fGLContext); 56 fGLContext = nullptr; 57 } 58 } 59 60 bool SkOSWindow::attach(SkBackEndTypes attachType, int msaaSampleCount, AttachmentInfo* info) { 61 this->createWindow(msaaSampleCount); 62 if (!fWindow) { 63 return false; 64 } 65 if (!fGLContext) { 66 fGLContext = SDL_GL_CreateContext(fWindow); 67 if (!fGLContext) { 68 report_sdl_error("Failed to create SDL GL context."); 69 return false; 70 } 71 glClearColor(0, 0, 0, 0); 72 glClearStencil(0); 73 glStencilMask(0xffffffff); 74 glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 75 } 76 77 if (SDL_GL_MakeCurrent(fWindow, fGLContext) != 0) { 78 report_sdl_error("Failed to make SDL GL context current."); 79 this->detach(); 80 return false; 81 } 82 83 info->fSampleCount = msaaSampleCount; 84 info->fStencilBits = 8; 85 86 glViewport(0, 0, SkScalarRoundToInt(this->width()), SkScalarRoundToInt(this->height())); 87 return true; 88 } 89 90 void SkOSWindow::present() { 91 if (!fWindow) { 92 return; 93 } 94 SDL_GL_SwapWindow(fWindow); 95 } 96 97 bool SkOSWindow::makeFullscreen() { 98 if (!fWindow) { 99 return false; 100 } 101 SDL_SetWindowFullscreen(fWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); 102 return true; 103 } 104 105 void SkOSWindow::setVsync(bool vsync) { 106 if (!fWindow) { 107 return; 108 } 109 SDL_GL_SetSwapInterval(vsync ? 1 : 0); 110 } 111 112 void SkOSWindow::closeWindow() { 113 this->destroyWindow(); 114 115 // Currently closing the window causes the app to quit. 116 SDL_Event event; 117 event.type = SDL_QUIT; 118 SDL_PushEvent(&event); 119 } 120 121 static SkKey convert_sdlkey_to_skkey(SDL_Keycode src) { 122 switch (src) { 123 case SDLK_UP: 124 return kUp_SkKey; 125 case SDLK_DOWN: 126 return kDown_SkKey; 127 case SDLK_LEFT: 128 return kLeft_SkKey; 129 case SDLK_RIGHT: 130 return kRight_SkKey; 131 case SDLK_HOME: 132 return kHome_SkKey; 133 case SDLK_END: 134 return kEnd_SkKey; 135 case SDLK_ASTERISK: 136 return kStar_SkKey; 137 case SDLK_HASH: 138 return kHash_SkKey; 139 case SDLK_0: 140 return k0_SkKey; 141 case SDLK_1: 142 return k1_SkKey; 143 case SDLK_2: 144 return k2_SkKey; 145 case SDLK_3: 146 return k3_SkKey; 147 case SDLK_4: 148 return k4_SkKey; 149 case SDLK_5: 150 return k5_SkKey; 151 case SDLK_6: 152 return k6_SkKey; 153 case SDLK_7: 154 return k7_SkKey; 155 case SDLK_8: 156 return k8_SkKey; 157 case SDLK_9: 158 return k9_SkKey; 159 default: 160 return kNONE_SkKey; 161 } 162 } 163 164 void SkOSWindow::createWindow(int msaaSampleCount) { 165 if (fWindowMSAASampleCount != msaaSampleCount) { 166 this->destroyWindow(); 167 } 168 if (fWindow) { 169 return; 170 } 171 uint32_t windowFlags = 172 #if defined(SK_BUILD_FOR_ANDROID) 173 SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN_DESKTOP | 174 SDL_WINDOW_ALLOW_HIGHDPI | 175 #endif 176 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; 177 178 // GL settings are part of SDL_WINDOW_OPENGL window creation arguments. 179 #if defined(SK_BUILD_FOR_ANDROID) 180 // TODO we should try and get a 3.0 context first 181 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); 182 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 183 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); 184 #else 185 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 186 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); 187 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 188 #endif 189 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); 190 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); 191 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 192 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); 193 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 194 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 195 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 196 #if defined(SK_BUILD_FOR_UNIX) 197 // Apparently MSAA request matches "slow caveat". Make SDL not set anything for caveat for MSAA 198 // by setting -1 for ACCELERATED_VISUAL. For non-MSAA, set ACCELERATED_VISUAL to 1 just for 199 // compatiblity with other platforms. 200 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, msaaSampleCount > 0 ? -1 : 1); 201 #else 202 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); 203 #endif 204 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, msaaSampleCount > 0 ? 1 : 0); 205 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, msaaSampleCount); 206 207 // This is an approximation for sizing purposes. 208 bool isInitialWindow = this->width() == 0 && this->height() == 0; 209 SkScalar windowWidth = isInitialWindow ? kInitialWindowWidth : this->width(); 210 SkScalar windowHeight = isInitialWindow ? kInitialWindowHeight : this->height(); 211 212 fWindow = SDL_CreateWindow(this->getTitle(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 213 windowWidth, windowHeight, windowFlags); 214 if (!fWindow) { 215 report_sdl_error("Failed to create SDL window."); 216 return; 217 } 218 fWindowMSAASampleCount = msaaSampleCount; 219 } 220 221 void SkOSWindow::destroyWindow() { 222 this->detach(); 223 if (fWindow) { 224 SDL_DestroyWindow(fWindow); 225 fWindow = nullptr; 226 fWindowMSAASampleCount = 0; 227 } 228 } 229 230 bool SkOSWindow::HasDirtyWindows() { 231 if (gCurrentWindow && gCurrentWindow->fWindow) { 232 return gCurrentWindow->isDirty(); 233 } 234 return false; 235 } 236 237 void SkOSWindow::UpdateDirtyWindows() { 238 if (gCurrentWindow && gCurrentWindow->fWindow) { 239 if (gCurrentWindow->isDirty()) { 240 // This will call present. 241 gCurrentWindow->update(nullptr); 242 } 243 } 244 } 245 246 void SkOSWindow::HandleEvent(const SDL_Event& event) { 247 switch (event.type) { 248 case SDL_MOUSEMOTION: 249 if (SkOSWindow* window = GetInstanceForWindowID(event.motion.windowID)) { 250 if (event.motion.state == SDL_PRESSED) { 251 window->handleClick(event.motion.x, event.motion.y, 252 SkView::Click::kMoved_State, nullptr); 253 } 254 } 255 break; 256 case SDL_MOUSEBUTTONDOWN: 257 case SDL_MOUSEBUTTONUP: 258 if (SkOSWindow* window = GetInstanceForWindowID(event.button.windowID)) { 259 window->handleClick(event.button.x, event.button.y, 260 event.button.state == SDL_PRESSED ? 261 SkView::Click::kDown_State : 262 SkView::Click::kUp_State, nullptr); 263 } 264 break; 265 case SDL_KEYDOWN: 266 if (SkOSWindow* window = GetInstanceForWindowID(event.key.windowID)) { 267 SDL_Keycode key = event.key.keysym.sym; 268 SkKey sk = convert_sdlkey_to_skkey(key); 269 if (kNONE_SkKey != sk) { 270 if (event.key.state == SDL_PRESSED) { 271 window->handleKey(sk); 272 } else { 273 window->handleKeyUp(sk); 274 } 275 } else if (key == SDLK_ESCAPE) { 276 window->closeWindow(); 277 } 278 } 279 break; 280 case SDL_TEXTINPUT: 281 if (SkOSWindow* window = GetInstanceForWindowID(event.text.windowID)) { 282 size_t len = strlen(event.text.text); 283 for (size_t i = 0; i < len; i++) { 284 window->handleChar((SkUnichar)event.text.text[i]); 285 } 286 } 287 break; 288 case SDL_WINDOWEVENT: 289 switch (event.window.event) { 290 case SDL_WINDOWEVENT_SHOWN: 291 // For initialization purposes, we resize upon first show. 292 // Fallthrough. 293 case SDL_WINDOWEVENT_SIZE_CHANGED: 294 if (SkOSWindow* window = GetInstanceForWindowID(event.window.windowID)) { 295 int w = 0; 296 int h = 0; 297 SDL_GetWindowSize(window->fWindow, &w, &h); 298 window->resize(w, h); 299 } 300 break; 301 case SDL_WINDOWEVENT_FOCUS_GAINED: 302 if (GetInstanceForWindowID(event.text.windowID)) { 303 SDL_StartTextInput(); 304 } 305 break; 306 default: 307 break; 308 } 309 break; 310 default: 311 break; 312 } 313 } 314 315 SkMSec gTimerDelay; 316 317 void SkOSWindow::RunEventLoop() { 318 for (;;) { 319 SkEvent::ServiceQueueTimer(); 320 bool hasMoreSkEvents = SkEvent::ProcessEvent(); 321 322 SDL_Event event; 323 bool hasSDLEvents = SDL_PollEvent(&event) == 1; 324 325 // Invalidations do not post to event loop, rather we just go through the 326 // windows for each event loop iteration. 327 bool hasDirtyWindows = HasDirtyWindows(); 328 329 if (!hasSDLEvents && !hasMoreSkEvents && !hasDirtyWindows) { 330 // If there is no SDL events, SkOSWindow updates or SkEvents 331 // to be done, wait for the SDL events. 332 if (gTimerDelay > 0) { 333 hasSDLEvents = SDL_WaitEventTimeout(&event, gTimerDelay) == 1; 334 } else { 335 hasSDLEvents = SDL_WaitEvent(&event) == 1; 336 } 337 } 338 while (hasSDLEvents) { 339 if (event.type == SDL_QUIT) { 340 return; 341 } 342 HandleEvent(event); 343 hasSDLEvents = SDL_PollEvent(&event); 344 } 345 UpdateDirtyWindows(); 346 } 347 } 348 349 void SkOSWindow::onSetTitle(const char title[]) { 350 if (!fWindow) { 351 return; 352 } 353 this->updateWindowTitle(); 354 } 355 356 void SkOSWindow::updateWindowTitle() { 357 SDL_SetWindowTitle(fWindow, this->getTitle()); 358 } 359 /////////////////////////////////////////////////////////////////////////////////////// 360 361 void SkEvent::SignalNonEmptyQueue() { 362 // nothing to do, since we spin on our event-queue 363 } 364 365 void SkEvent::SignalQueueTimer(SkMSec delay) { 366 gTimerDelay = delay; 367 } 368 369 ////////////////////////////////////////////////////////////////////////////////////////////// 370 371 #include "SkApplication.h" 372 #include "SkEvent.h" 373 #include "SkWindow.h" 374 375 #if defined(SK_BUILD_FOR_ANDROID) 376 int SDL_main(int argc, char** argv) { 377 #else 378 int main(int argc, char** argv) { 379 #endif 380 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { 381 report_sdl_error("Failed to init SDL."); 382 return -1; 383 } 384 385 application_init(); 386 387 SkOSWindow* window = create_sk_window(nullptr, argc, argv); 388 389 // drain any events that occurred before |window| was assigned. 390 while (SkEvent::ProcessEvent()); 391 392 SkOSWindow::RunEventLoop(); 393 394 delete window; 395 application_term(); 396 397 SDL_Quit(); 398 399 return 0; 400 } 401