1 /*
2    Copyright (C) 1999/2000/2001/2004  Alexandre Courbot
3    Copyright (C) 2016  Kai Sterker
4    Part of the Adonthell Project <http://adonthell.nongnu.org>
5 
6    Adonthell is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    Adonthell is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Adonthell.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 /**
21  * @file   screen.cc
22  * @author Alexandre Courbot <alexandrecourbot@linuxgames.com>
23  * @author Kai Sterker
24  *
25  * @brief  Defines the screen class.
26  *
27  *
28  */
29 
30 #include <config.h>
31 #include "screen.h"
32 #include "game.h"
33 #include <iostream>
34 #include <sstream>
35 #include <algorithm>
36 
37 using namespace std;
38 
39 #if !defined(HAVE_DECL_SDL_WINDOW_ALLOW_HIGHDPI) || HAVE_DECL_SDL_WINDOW_ALLOW_HIGHDPI == 0
40 #define SDL_WINDOW_ALLOW_HIGHDPI 0
41 #endif
42 
43 #ifndef HAVE_SDL_GETDISPLAYUSABLEBOUNDS
44 #define SDL_GetDisplayUsableBounds SDL_GetDisplayBounds
45 #endif
46 
47 surface screen::display;
48 u_int8 screen::bytes_per_pixel_ = 0;
49 u_int32 screen::trans = 0;
50 SDL_Window *screen::Window = NULL;
51 SDL_Renderer *screen::Renderer = NULL;
52 
53 u_int8 screen::mode_ = 0;
54 u_int8 screen::scale_;
55 SDL_Rect screen::clip_rect_ = {};
56 SDL_DisplayMode screen::fullscreen_mode = {};
57 
cleanup()58 void screen::cleanup()
59 {
60     if (Renderer) SDL_DestroyRenderer(Renderer);
61     if (Window) SDL_DestroyWindow(Window);
62 
63     Renderer = NULL;
64     Window = NULL;
65 }
66 
init(u_int16 nl,u_int16 nh,u_int8 depth,const config & myconfig)67 bool screen::init (u_int16 nl, u_int16 nh, u_int8 depth, const config & myconfig)
68 {
69 	u_int8 screen = myconfig.display;
70 	u_int8 screen_mode = myconfig.screen_mode;
71 
72 #if defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND)
73 	static std::string wm_class = "SDL_VIDEO_X11_WMCLASS=" + myconfig.game_name;
74 	putenv ((char *) wm_class.c_str ());
75 #endif
76 
77 #if defined(SDL_HINT_RENDER_BATCHING)
78     SDL_SetHint (SDL_HINT_RENDER_BATCHING, "1");
79 #endif
80 
81     if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) < 0)
82     {
83     	std::cout << "Couldn't init SDL: " << SDL_GetError () << std::endl;
84     	return false;
85     }
86 
87     SDL_SetHint (SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
88 
89 	int availableDisplays = SDL_GetNumVideoDisplays();
90 	if (availableDisplays < 1)
91 	{
92     	std::cout << "Couldn't init screen: " << SDL_GetError () << std::endl;
93     	return false;
94 	}
95 
96 	if (screen >= availableDisplays)
97 	{
98 		// if the requested display does not exist, pick the first one
99 		screen = 0;
100 	}
101 
102 	if (screen_mode > 2)
103 	{
104 		// if the requested screen mode is invalid, fallback to window mode
105 		screen_mode = 0;
106 	}
107 
108 	mode_ = screen_mode;
109 	scale_ = get_scale_for_display(screen, nl, nh);
110 
111 	// set window flags
112 	unsigned int flags = SDL_WINDOW_ALLOW_HIGHDPI;
113 	switch (screen_mode)
114 	{
115 		case 1:
116 		{
117 			flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
118 			break;
119 		}
120 		case 2:
121 		{
122 			flags |= SDL_WINDOW_FULLSCREEN;
123 			break;
124 		}
125 	}
126 
127     memset(&fullscreen_mode, 0, sizeof(SDL_DisplayMode));
128     fullscreen_mode.format = SDL_PIXELFORMAT_RGB888;
129 
130     nl *= scale_;
131     nh *= scale_;
132 
133 	SDL_ShowCursor(SDL_DISABLE);
134 
135     Window = SDL_CreateWindow ("Adonthell", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, nl, nh, flags);
136     if (!Window)
137     {
138         std::cout << "Failed creating window: " << SDL_GetError() << std::endl;
139     	return false;
140     }
141 #ifdef WIN32
142     const string icon_name = game::find_file("gfx/icon32.bmp");
143     if (!icon_name.empty())
144     {
145     	SDL_Surface* icon = SDL_LoadBMP(icon_name.c_str());
146 		if (icon != NULL)
147 		{
148 			SDL_SetWindowIcon(Window, icon);
149 			SDL_FreeSurface(icon);
150 		}
151     }
152 #endif
153     if (SDL_SetWindowDisplayMode(Window, &fullscreen_mode) < 0)
154     {
155         std::cout << "Failed setting display mode: " << SDL_GetError() << std::endl;
156     	return false;
157     }
158     SDL_ShowWindow(Window);
159 
160     Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
161     if (!Renderer)
162     {
163     	std::cout << "Failed creating accelerated renderer: " << SDL_GetError() << std::endl;
164         Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_SOFTWARE);
165         if (!Renderer)
166         {
167         	std::cout << "Failed creating renderer: " << SDL_GetError() << std::endl;
168         	return false;
169         }
170     }
171 
172     display.set_length(nl/scale_);
173     display.set_height(nh/scale_);
174 
175     // check if we have a HIGH_DPI window, in which case we need to update our scale
176 	update_scale();
177 
178     // Setting up transparency color
179     trans = display.map_color(255, 0, 255, SDL_ALPHA_OPAQUE);
180 
181 	return true;
182 }
183 
info()184 string screen::info ()
185 {
186     SDL_version version_info;
187     SDL_RendererInfo render_info;
188     std::ostringstream temp;
189 
190     SDL_GetVersion(&version_info);
191 
192     if (Renderer)
193     {
194     	SDL_GetRendererInfo(Renderer, &render_info);
195     }
196     else
197     {
198     	render_info.name = "not yet initialized";
199     	render_info.flags = 0;
200     }
201 
202     temp << "Video information: " << std::endl
203          << "Platform:          " << SDL_GetPlatform() << std::endl
204          << "Version:           " << "SDL " <<(int) version_info.major << "." << (int) version_info.minor << "." << (int) version_info.patch << " " << SDL_GetRevision() << std::endl
205          << "Video driver used: " << SDL_GetCurrentVideoDriver() << std::endl
206          << "Renderer used:     " << render_info.name << std::endl
207          << "HW Accelerated:    " << ((render_info.flags & SDL_RENDERER_ACCELERATED) == SDL_RENDERER_ACCELERATED ? "Yes" : "No") << std::endl
208          << "Display Format:    " << SDL_GetPixelFormatName (format()) << std::endl
209 		 << "Screen Size        " << (int)length()*scale() << "x" << (int)height()*scale() << std::endl
210          << "Fullscreen:        " << (mode() ? "Yes" : "No") << std::endl
211          << std::ends;
212 
213     return temp.str ();
214 }
215 
216 #ifdef DEBUG
get_mode_str(const u_int8 & m)217 static const char* get_mode_str(const u_int8 & m)
218 {
219 	switch(m)
220 	{
221 	case 0: return "window";
222 	case 1: return "letterbox";
223 	case 2: return "fullscreen";
224 	default: return "unknown";
225 	}
226 }
227 #endif
228 
set_fullscreen(const u_int8 & m)229 bool screen::set_fullscreen (const u_int8 & m)
230 {
231 	bool r = false;
232     if (mode_ != m)
233     {
234 #ifdef DEBUG
235     	std::cout << "Switching from " << get_mode_str(mode_) << " to " << get_mode_str(m) << std::endl;
236 #endif
237     	if (mode_ != 0)
238     	{
239 			r = SDL_SetWindowFullscreen(Window, SDL_FALSE) == 0;
240 			if (!r)
241 			{
242 	        	std::cout << "Failed to leave fullscreen mode: " << SDL_GetError() << std::endl;
243 				return false;
244 			}
245     	}
246 
247     	// update mode before getting new scale
248     	mode_ = m;
249 
250     	u_int8 screen = SDL_GetWindowDisplayIndex(Window);
251     	u_int8 new_scale = get_scale_for_display(screen, length(), height());
252     	if (new_scale != scale())
253     	{
254 #ifdef DEBUG
255         	std::cout << "Scale changed from " << (int)scale_ << " to " << (int)new_scale << std::endl;
256 #endif
257     		SDL_SetWindowSize(Window, length()*new_scale, height()*new_scale);
258     	}
259 
260     	switch(mode_)
261     	{
262 			case 0:
263 			{
264 				SDL_SetWindowPosition(Window, SDL_WINDOWPOS_CENTERED_DISPLAY(screen), SDL_WINDOWPOS_CENTERED_DISPLAY(screen));
265 				break;
266 			}
267 			case 1:
268 			{
269 				r = SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0;
270 				break;
271 			}
272 			case 2:
273 			{
274 				SDL_Rect bounds;
275 				SDL_GetDisplayBounds(screen, &bounds);
276 				SDL_SetWindowPosition(Window, bounds.x, bounds.y);
277 				r = SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN) == 0;
278 				break;
279 			}
280     	}
281 
282         if (r)
283 		{
284         	update_scale();
285 		}
286         else
287         {
288         	std::cout << "Failed to enter fullscreen mode: " << SDL_GetError() << std::endl;
289         }
290 
291         return r;
292     }
293 
294     return false;
295 }
296 
get_scale_for_display(u_int8 screen,u_int16 nl,u_int16 nh)297 u_int8 screen::get_scale_for_display(u_int8 screen, u_int16 nl, u_int16 nh)
298 {
299 	SDL_Rect bounds;
300 	switch (mode_)
301 	{
302 		case 0:
303 		{
304 			// window mode
305 			SDL_Delay(250);
306 			SDL_GetDisplayUsableBounds(screen, &bounds);
307 			break;
308 		}
309 		case 1:
310 		{
311 			// letterbox mode with aspect ratio preserved
312 			SDL_GetDisplayBounds(screen, &bounds);
313 			break;
314 		}
315 		case 2:
316 		{
317 			bounds.x = 0;
318 			bounds.y = 0;
319 			bounds.w = nl * 2;
320 			bounds.h = nh * 2;
321 
322 			// fullscreen mode at highest supported multiple of 320x240
323 			const int num_modes = SDL_GetNumDisplayModes(screen);
324 			SDL_DisplayMode mode;
325 			for (int i = 0; i < num_modes; ++i)
326 			{
327 				if (SDL_GetDisplayMode(screen, i, &mode) > -1)
328 				{
329 					if (mode.w % nl == 0 && mode.h % nh == 0)
330 					{
331 						bounds.w = mode.w;
332 						bounds.h = mode.h;
333 						break;
334 					}
335 				}
336 			}
337 			break;
338 		}
339 	}
340 
341 	int scale_x = bounds.w / nl;
342 	int scale_y = bounds.h / nh;
343 
344 	return std::max(1, scale_x > scale_y ? scale_y : scale_x);
345 }
346 
update_scale()347 void screen::update_scale()
348 {
349 	int w, h;
350 
351 	if (SDL_GetRendererOutputSize(Renderer, &w, &h) == 0)
352 	{
353 		int scale_x = w / length();
354 		int scale_y = h / height();
355 
356 		scale_ = scale_x > scale_y ? scale_y : scale_x;
357 	}
358 
359 	if (mode_ == 1)
360 	{
361 		// center viewport in letterbox mode
362 		clip_rect_.x = (w - length() * scale_) / 2;
363 		clip_rect_.y = (h - height() * scale_) / 2;
364 		clip_rect_.w = length() * scale_;
365 		clip_rect_.h = height() * scale_;
366 
367 		SDL_RenderSetClipRect(Renderer, &clip_rect_);
368 	}
369 	else
370 	{
371 		// no rendering offset required when running in window or fullscreen modes
372 		clip_rect_.x = 0;
373 		clip_rect_.y = 0;
374 		clip_rect_.w = length() * scale_;
375 		clip_rect_.h = height() * scale_;
376 
377 		SDL_RenderSetClipRect(Renderer, NULL);
378 	}
379 
380 #ifdef DEBUG
381 	std::cout << "Mode = " << get_mode_str(mode_) << ", X = " << offset_x() << ", Y = " << offset_y()
382 			<< ", Width = " << w << ", Height = " << h << ", Scale = "
383 			<< (int) (scale_) << std::endl;
384 #endif
385 }
386 
transition(u_int16 i)387 void screen::transition (u_int16 i)
388 {
389     display.fillrect (0, 0, i, screen::height (), 0);
390     display.fillrect (screen::length () - i, 0, i, screen::height (), 0);
391     display.fillrect (0, 0, screen::length (), i, 0);
392     display.fillrect (0, screen::height () - i, screen::length (), i, 0);
393 }
394