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 
report_sdl_error(const char * failure)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 }
SkOSWindow(void *)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 
~SkOSWindow()39 SkOSWindow::~SkOSWindow() {
40     this->destroyWindow();
41     gCurrentWindow = nullptr;
42 }
43 
GetInstanceForWindowID(Uint32 windowID)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 
detach()53 void SkOSWindow::detach() {
54     if (fGLContext) {
55         SDL_GL_DeleteContext(fGLContext);
56         fGLContext = nullptr;
57     }
58 }
59 
attach(SkBackEndTypes attachType,int msaaSampleCount,AttachmentInfo * info)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 
present()90 void SkOSWindow::present() {
91     if (!fWindow) {
92         return;
93     }
94     SDL_GL_SwapWindow(fWindow);
95 }
96 
makeFullscreen()97 bool SkOSWindow::makeFullscreen() {
98     if (!fWindow) {
99         return false;
100     }
101     SDL_SetWindowFullscreen(fWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
102     return true;
103 }
104 
setVsync(bool vsync)105 void SkOSWindow::setVsync(bool vsync) {
106     if (!fWindow) {
107         return;
108     }
109     SDL_GL_SetSwapInterval(vsync ? 1 : 0);
110 }
111 
closeWindow()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 
convert_sdlkey_to_skkey(SDL_Keycode src)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 
createWindow(int msaaSampleCount)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 
destroyWindow()221 void SkOSWindow::destroyWindow() {
222     this->detach();
223     if (fWindow) {
224         SDL_DestroyWindow(fWindow);
225         fWindow = nullptr;
226         fWindowMSAASampleCount = 0;
227     }
228 }
229 
HasDirtyWindows()230 bool SkOSWindow::HasDirtyWindows() {
231     if (gCurrentWindow && gCurrentWindow->fWindow) {
232         return gCurrentWindow->isDirty();
233     }
234     return false;
235 }
236 
UpdateDirtyWindows()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 
HandleEvent(const SDL_Event & event)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 
RunEventLoop()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 
onSetTitle(const char title[])349 void SkOSWindow::onSetTitle(const char title[]) {
350     if (!fWindow) {
351         return;
352     }
353     this->updateWindowTitle();
354 }
355 
updateWindowTitle()356 void SkOSWindow::updateWindowTitle() {
357     SDL_SetWindowTitle(fWindow, this->getTitle());
358 }
359 ///////////////////////////////////////////////////////////////////////////////////////
360 
SignalNonEmptyQueue()361 void SkEvent::SignalNonEmptyQueue() {
362     // nothing to do, since we spin on our event-queue
363 }
364 
SignalQueueTimer(SkMSec delay)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)
SDL_main(int argc,char ** argv)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