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
18const int kInitialWindowWidth = 640;
19const int kInitialWindowHeight = 480;
20static SkOSWindow* gCurrentWindow;
21
22static 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}
28SkOSWindow::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
39SkOSWindow::~SkOSWindow() {
40    this->destroyWindow();
41    gCurrentWindow = nullptr;
42}
43
44SkOSWindow* 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
53void SkOSWindow::detach() {
54    if (fGLContext) {
55        SDL_GL_DeleteContext(fGLContext);
56        fGLContext = nullptr;
57    }
58}
59
60bool 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
90void SkOSWindow::present() {
91    if (!fWindow) {
92        return;
93    }
94    SDL_GL_SwapWindow(fWindow);
95}
96
97bool SkOSWindow::makeFullscreen() {
98    if (!fWindow) {
99        return false;
100    }
101    SDL_SetWindowFullscreen(fWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
102    return true;
103}
104
105void SkOSWindow::setVsync(bool vsync) {
106    if (!fWindow) {
107        return;
108    }
109    SDL_GL_SetSwapInterval(vsync ? 1 : 0);
110}
111
112void 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
121static 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
164void 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
221void SkOSWindow::destroyWindow() {
222    this->detach();
223    if (fWindow) {
224        SDL_DestroyWindow(fWindow);
225        fWindow = nullptr;
226        fWindowMSAASampleCount = 0;
227    }
228}
229
230bool SkOSWindow::HasDirtyWindows() {
231    if (gCurrentWindow && gCurrentWindow->fWindow) {
232        return gCurrentWindow->isDirty();
233    }
234    return false;
235}
236
237void SkOSWindow::UpdateDirtyWindows() {
238    if (gCurrentWindow && gCurrentWindow->fWindow) {
239        if (gCurrentWindow->isDirty()) {
240            // This will call present.
241            gCurrentWindow->update(nullptr);
242        }
243    }
244}
245
246void 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
315SkMSec gTimerDelay;
316
317void 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
349void SkOSWindow::onSetTitle(const char title[]) {
350    if (!fWindow) {
351        return;
352    }
353    this->updateWindowTitle();
354}
355
356void SkOSWindow::updateWindowTitle() {
357    SDL_SetWindowTitle(fWindow, this->getTitle());
358}
359///////////////////////////////////////////////////////////////////////////////////////
360
361void SkEvent::SignalNonEmptyQueue() {
362    // nothing to do, since we spin on our event-queue
363}
364
365void 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)
376int SDL_main(int argc, char** argv) {
377#else
378int 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