1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #include "SDL_lib.hxx" |
19 | #include "bspf.hxx" |
20 | #include "Logger.hxx" |
21 | |
22 | #include "Console.hxx" |
23 | #include "OSystem.hxx" |
24 | #include "Settings.hxx" |
25 | |
26 | #include "ThreadDebugging.hxx" |
27 | #include "FBSurfaceSDL2.hxx" |
28 | #include "FrameBufferSDL2.hxx" |
29 | |
30 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
31 | FrameBufferSDL2::FrameBufferSDL2(OSystem& osystem) |
32 | : FrameBuffer(osystem), |
33 | myWindow(nullptr), |
34 | myRenderer(nullptr), |
35 | myCenter(false) |
36 | { |
37 | ASSERT_MAIN_THREAD; |
38 | |
39 | // Initialize SDL2 context |
40 | if(SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) |
41 | { |
42 | ostringstream buf; |
43 | buf << "ERROR: Couldn't initialize SDL: " << SDL_GetError() << endl; |
44 | Logger::error(buf.str()); |
45 | throw runtime_error("FATAL ERROR" ); |
46 | } |
47 | Logger::debug("FrameBufferSDL2::FrameBufferSDL2 SDL_Init()" ); |
48 | |
49 | // We need a pixel format for palette value calculations |
50 | // It's done this way (vs directly accessing a FBSurfaceSDL2 object) |
51 | // since the structure may be needed before any FBSurface's have |
52 | // been created |
53 | myPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888); |
54 | |
55 | myWindowedPos = myOSystem.settings().getPoint("windowedpos" ); |
56 | } |
57 | |
58 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
59 | FrameBufferSDL2::~FrameBufferSDL2() |
60 | { |
61 | ASSERT_MAIN_THREAD; |
62 | |
63 | SDL_FreeFormat(myPixelFormat); |
64 | |
65 | if(myRenderer) |
66 | { |
67 | // Make sure to free surfaces/textures before destroying the renderer itself |
68 | // Most platforms are fine with doing this in either order, but it seems |
69 | // that OpenBSD in particular crashes when attempting to destroy textures |
70 | // *after* the renderer is already destroyed |
71 | freeSurfaces(); |
72 | |
73 | SDL_DestroyRenderer(myRenderer); |
74 | myRenderer = nullptr; |
75 | } |
76 | if(myWindow) |
77 | { |
78 | SDL_SetWindowFullscreen(myWindow, 0); // on some systems, a crash occurs |
79 | // when destroying fullscreen window |
80 | SDL_DestroyWindow(myWindow); |
81 | myWindow = nullptr; |
82 | } |
83 | SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER); |
84 | } |
85 | |
86 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
87 | void FrameBufferSDL2::queryHardware(vector<Common::Size>& fullscreenRes, |
88 | vector<Common::Size>& windowedRes, |
89 | VariantList& renderers) |
90 | { |
91 | ASSERT_MAIN_THREAD; |
92 | |
93 | // Get number of displays (for most systems, this will be '1') |
94 | myNumDisplays = SDL_GetNumVideoDisplays(); |
95 | |
96 | // First get the maximum fullscreen desktop resolution |
97 | SDL_DisplayMode display; |
98 | for(int i = 0; i < myNumDisplays; ++i) |
99 | { |
100 | SDL_GetDesktopDisplayMode(i, &display); |
101 | fullscreenRes.emplace_back(display.w, display.h); |
102 | } |
103 | |
104 | // Now get the maximum windowed desktop resolution |
105 | // Try to take into account taskbars, etc, if available |
106 | #if SDL_VERSION_ATLEAST(2,0,5) |
107 | SDL_Rect r; |
108 | for(int i = 0; i < myNumDisplays; ++i) |
109 | { |
110 | // Display bounds minus dock |
111 | SDL_GetDisplayUsableBounds(i, &r); // Requires SDL-2.0.5 or higher |
112 | windowedRes.emplace_back(r.w, r.h); |
113 | } |
114 | #else |
115 | for(int i = 0; i < myNumDisplays; ++i) |
116 | { |
117 | SDL_GetDesktopDisplayMode(i, &display); |
118 | windowedRes.emplace_back(display.w, display.h); |
119 | } |
120 | #endif |
121 | |
122 | struct RenderName |
123 | { |
124 | string sdlName; |
125 | string stellaName; |
126 | }; |
127 | // Create name map for all currently known SDL renderers |
128 | const int NUM_RENDERERS = 5; |
129 | static const RenderName RENDERER_NAMES[NUM_RENDERERS] = { |
130 | { "direct3d" , "Direct3D" }, |
131 | { "opengl" , "OpenGL" }, |
132 | { "opengles" , "OpenGLES" }, |
133 | { "opengles2" , "OpenGLES2" }, |
134 | { "software" , "Software" } |
135 | }; |
136 | |
137 | int numDrivers = SDL_GetNumRenderDrivers(); |
138 | for(int i = 0; i < numDrivers; ++i) |
139 | { |
140 | SDL_RendererInfo info; |
141 | if(SDL_GetRenderDriverInfo(i, &info) == 0) |
142 | { |
143 | // Map SDL names into nicer Stella names (if available) |
144 | bool found = false; |
145 | for(int j = 0; j < NUM_RENDERERS; ++j) |
146 | { |
147 | if(RENDERER_NAMES[j].sdlName == info.name) |
148 | { |
149 | VarList::push_back(renderers, RENDERER_NAMES[j].stellaName, info.name); |
150 | found = true; |
151 | break; |
152 | } |
153 | } |
154 | if(!found) |
155 | VarList::push_back(renderers, info.name, info.name); |
156 | } |
157 | } |
158 | } |
159 | |
160 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
161 | Int32 FrameBufferSDL2::getCurrentDisplayIndex() |
162 | { |
163 | ASSERT_MAIN_THREAD; |
164 | |
165 | return SDL_GetWindowDisplayIndex(myWindow); |
166 | } |
167 | |
168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
169 | void FrameBufferSDL2::updateWindowedPos() |
170 | { |
171 | ASSERT_MAIN_THREAD; |
172 | |
173 | // only save if the window is not centered and not in full screen mode |
174 | if (!myCenter && myWindow && !(SDL_GetWindowFlags(myWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP)) |
175 | { |
176 | // save current windowed position |
177 | SDL_GetWindowPosition(myWindow, &myWindowedPos.x, &myWindowedPos.y); |
178 | myOSystem.settings().setValue("windowedpos" , myWindowedPos); |
179 | } |
180 | } |
181 | |
182 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
183 | bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) |
184 | { |
185 | ASSERT_MAIN_THREAD; |
186 | |
187 | // If not initialized by this point, then immediately fail |
188 | if(SDL_WasInit(SDL_INIT_VIDEO) == 0) |
189 | return false; |
190 | |
191 | Int32 displayIndex = mode.fsIndex; |
192 | if (displayIndex == -1) |
193 | { |
194 | // windowed mode |
195 | if (myWindow) |
196 | { |
197 | // Show it on same screen as the previous window |
198 | displayIndex = SDL_GetWindowDisplayIndex(myWindow); |
199 | } |
200 | if (displayIndex < 0) |
201 | { |
202 | // fallback to the last used screen if still existing |
203 | displayIndex = std::min(myNumDisplays, myOSystem.settings().getInt("display" )); |
204 | } |
205 | } |
206 | |
207 | // save and get last windowed window's position |
208 | updateWindowedPos(); |
209 | |
210 | // Always recreate renderer (some systems need this) |
211 | if(myRenderer) |
212 | { |
213 | SDL_DestroyRenderer(myRenderer); |
214 | myRenderer = nullptr; |
215 | } |
216 | |
217 | int posX, posY; |
218 | |
219 | myCenter = myOSystem.settings().getBool("center" ); |
220 | if (myCenter) |
221 | posX = posY = SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex); |
222 | else |
223 | { |
224 | posX = myWindowedPos.x; |
225 | posY = myWindowedPos.y; |
226 | |
227 | // make sure the window is at least partially visibile |
228 | int x0 = 0, y0 = 0, x1 = 0, y1 = 0; |
229 | |
230 | for (int display = SDL_GetNumVideoDisplays() - 1; display >= 0; display--) |
231 | { |
232 | SDL_Rect rect; |
233 | |
234 | if (!SDL_GetDisplayUsableBounds(display, &rect)) |
235 | { |
236 | x0 = std::min(x0, rect.x); |
237 | y0 = std::min(y0, rect.y); |
238 | x1 = std::max(x1, rect.x + rect.w); |
239 | y1 = std::max(y1, rect.y + rect.h); |
240 | } |
241 | } |
242 | posX = BSPF::clamp(posX, x0 - Int32(mode.screen.w) + 50, x1 - 50); |
243 | posY = BSPF::clamp(posY, y0 + 50, y1 - 50); |
244 | } |
245 | uInt32 flags = mode.fsIndex != -1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0; |
246 | |
247 | // macOS seems to have issues with destroying the window, and wants to |
248 | // keep the same handle |
249 | // Problem is, doing so on other platforms results in flickering when |
250 | // toggling fullscreen windowed mode |
251 | // So we have a special case for macOS |
252 | #ifndef BSPF_MACOS |
253 | // Don't re-create the window if its size hasn't changed, as it's not |
254 | // necessary, and causes flashing in fullscreen mode |
255 | if(myWindow) |
256 | { |
257 | int w, h; |
258 | SDL_GetWindowSize(myWindow, &w, &h); |
259 | if(uInt32(w) != mode.screen.w || uInt32(h) != mode.screen.h) |
260 | { |
261 | SDL_DestroyWindow(myWindow); |
262 | myWindow = nullptr; |
263 | } |
264 | } |
265 | if(myWindow) |
266 | { |
267 | // Even though window size stayed the same, the title may have changed |
268 | SDL_SetWindowTitle(myWindow, title.c_str()); |
269 | SDL_SetWindowPosition(myWindow, posX, posY); |
270 | } |
271 | #else |
272 | // macOS wants to *never* re-create the window |
273 | // This sometimes results in the window being resized *after* it's displayed, |
274 | // but at least the code works and doesn't crash |
275 | if(myWindow) |
276 | { |
277 | SDL_SetWindowFullscreen(myWindow, flags); |
278 | SDL_SetWindowSize(myWindow, mode.screen.w, mode.screen.h); |
279 | SDL_SetWindowPosition(myWindow, posX, posY); |
280 | SDL_SetWindowTitle(myWindow, title.c_str()); |
281 | } |
282 | #endif |
283 | else |
284 | { |
285 | myWindow = SDL_CreateWindow(title.c_str(), posX, posY, |
286 | mode.screen.w, mode.screen.h, flags); |
287 | if(myWindow == nullptr) |
288 | { |
289 | string msg = "ERROR: Unable to open SDL window: " + string(SDL_GetError()); |
290 | Logger::error(msg); |
291 | return false; |
292 | } |
293 | setWindowIcon(); |
294 | } |
295 | |
296 | uInt32 renderFlags = SDL_RENDERER_ACCELERATED; |
297 | if(myOSystem.settings().getBool("vsync" )) // V'synced blits option |
298 | renderFlags |= SDL_RENDERER_PRESENTVSYNC; |
299 | const string& video = myOSystem.settings().getString("video" ); // Render hint |
300 | if(video != "" ) |
301 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, video.c_str()); |
302 | myRenderer = SDL_CreateRenderer(myWindow, -1, renderFlags); |
303 | if(myRenderer == nullptr) |
304 | { |
305 | string msg = "ERROR: Unable to create SDL renderer: " + string(SDL_GetError()); |
306 | Logger::error(msg); |
307 | return false; |
308 | } |
309 | clear(); |
310 | |
311 | SDL_RendererInfo renderinfo; |
312 | if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0) |
313 | myOSystem.settings().setValue("video" , renderinfo.name); |
314 | |
315 | return true; |
316 | } |
317 | |
318 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
319 | void FrameBufferSDL2::setTitle(const string& title) |
320 | { |
321 | ASSERT_MAIN_THREAD; |
322 | |
323 | myScreenTitle = title; |
324 | |
325 | if(myWindow) |
326 | SDL_SetWindowTitle(myWindow, title.c_str()); |
327 | } |
328 | |
329 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
330 | string FrameBufferSDL2::about() const |
331 | { |
332 | ASSERT_MAIN_THREAD; |
333 | |
334 | ostringstream out; |
335 | out << "Video system: " << SDL_GetCurrentVideoDriver() << endl; |
336 | SDL_RendererInfo info; |
337 | if(SDL_GetRendererInfo(myRenderer, &info) >= 0) |
338 | { |
339 | out << " Renderer: " << info.name << endl; |
340 | if(info.max_texture_width > 0 && info.max_texture_height > 0) |
341 | out << " Max texture: " << info.max_texture_width << "x" |
342 | << info.max_texture_height << endl; |
343 | out << " Flags: " |
344 | << ((info.flags & SDL_RENDERER_PRESENTVSYNC) ? "+" : "-" ) << "vsync, " |
345 | << ((info.flags & SDL_RENDERER_ACCELERATED) ? "+" : "-" ) << "accel" |
346 | << endl; |
347 | } |
348 | return out.str(); |
349 | } |
350 | |
351 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
352 | void FrameBufferSDL2::showCursor(bool show) |
353 | { |
354 | ASSERT_MAIN_THREAD; |
355 | |
356 | SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE); |
357 | } |
358 | |
359 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
360 | void FrameBufferSDL2::grabMouse(bool grab) |
361 | { |
362 | ASSERT_MAIN_THREAD; |
363 | |
364 | SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE); |
365 | } |
366 | |
367 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
368 | bool FrameBufferSDL2::fullScreen() const |
369 | { |
370 | ASSERT_MAIN_THREAD; |
371 | |
372 | #ifdef WINDOWED_SUPPORT |
373 | return SDL_GetWindowFlags(myWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP; |
374 | #else |
375 | return true; |
376 | #endif |
377 | } |
378 | |
379 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
380 | void FrameBufferSDL2::renderToScreen() |
381 | { |
382 | ASSERT_MAIN_THREAD; |
383 | |
384 | // Show all changes made to the renderer |
385 | SDL_RenderPresent(myRenderer); |
386 | } |
387 | |
388 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
389 | void FrameBufferSDL2::setWindowIcon() |
390 | { |
391 | ASSERT_MAIN_THREAD; |
392 | |
393 | #if !defined(BSPF_MACOS) && !defined(RETRON77) |
394 | #include "stella_icon.hxx" |
395 | |
396 | SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(stella_icon, 32, 32, 32, |
397 | 32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); |
398 | SDL_SetWindowIcon(myWindow, surface); |
399 | SDL_FreeSurface(surface); |
400 | #endif |
401 | } |
402 | |
403 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
404 | unique_ptr<FBSurface> |
405 | FrameBufferSDL2::createSurface(uInt32 w, uInt32 h, const uInt32* data) const |
406 | { |
407 | return make_unique<FBSurfaceSDL2>(const_cast<FrameBufferSDL2&>(*this), w, h, data); |
408 | } |
409 | |
410 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
411 | void FrameBufferSDL2::readPixels(uInt8* pixels, uInt32 pitch, |
412 | const Common::Rect& rect) const |
413 | { |
414 | ASSERT_MAIN_THREAD; |
415 | |
416 | SDL_Rect r; |
417 | r.x = rect.x(); r.y = rect.y(); |
418 | r.w = rect.w(); r.h = rect.h(); |
419 | |
420 | SDL_RenderReadPixels(myRenderer, &r, 0, pixels, pitch); |
421 | } |
422 | |
423 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
424 | void FrameBufferSDL2::clear() |
425 | { |
426 | ASSERT_MAIN_THREAD; |
427 | |
428 | SDL_RenderClear(myRenderer); |
429 | } |
430 | |