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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31FrameBufferSDL2::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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
59FrameBufferSDL2::~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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
161Int32 FrameBufferSDL2::getCurrentDisplayIndex()
162{
163 ASSERT_MAIN_THREAD;
164
165 return SDL_GetWindowDisplayIndex(myWindow);
166}
167
168// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
183bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
319void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
330string 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
352void FrameBufferSDL2::showCursor(bool show)
353{
354 ASSERT_MAIN_THREAD;
355
356 SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
357}
358
359// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
360void FrameBufferSDL2::grabMouse(bool grab)
361{
362 ASSERT_MAIN_THREAD;
363
364 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
365}
366
367// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
368bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
380void FrameBufferSDL2::renderToScreen()
381{
382 ASSERT_MAIN_THREAD;
383
384 // Show all changes made to the renderer
385 SDL_RenderPresent(myRenderer);
386}
387
388// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
389void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
404unique_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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
411void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
424void FrameBufferSDL2::clear()
425{
426 ASSERT_MAIN_THREAD;
427
428 SDL_RenderClear(myRenderer);
429}
430