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