1 /**************************************************************
2 
3    display.cpp - Display manager
4 
5    ---------------------------------------------------------
6 
7    Switchres   Modeline generation engine for emulation
8 
9    License     GPL-2.0+
10    Copyright   2010-2021 Chris Kennedy, Antonio Giner,
11                          Alexandre Wodarczyk, Gil Delescluse
12 
13  **************************************************************/
14 
15 #include <stdio.h>
16 #include "display.h"
17 #if defined(_WIN32)
18 #include "display_windows.h"
19 #elif defined(__linux__)
20 #include <string.h>
21 #include "display_linux.h"
22 #endif
23 #include "log.h"
24 
25 //============================================================
26 //  display_manager::make
27 //============================================================
28 
make(display_settings * ds)29 display_manager *display_manager::make(display_settings *ds)
30 {
31 	display_manager *display = nullptr;
32 
33 #if defined(_WIN32)
34 	display = new windows_display(ds);
35 #elif defined(__linux__)
36 	display = new linux_display(ds);
37 #endif
38 
39 	return display;
40 }
41 
42 //============================================================
43 //  display_manager::parse_options
44 //============================================================
45 
parse_options()46 void display_manager::parse_options()
47 {
48 	// Get user_mode as <w>x<h>@<r>
49 	set_user_mode(&m_ds.user_mode);
50 
51 	// Get user defined modeline (overrides user_mode)
52 	modeline user_mode = {};
53 	if (m_ds.modeline_generation)
54 	{
55 		if (modeline_parse(m_ds.user_modeline, &user_mode))
56 		{
57 			user_mode.type |= MODE_USER_DEF;
58 			set_user_mode(&user_mode);
59 		}
60 	}
61 
62 	// Get monitor specs
63 	if (user_mode.hactive)
64 	{
65 		modeline_to_monitor_range(range, &user_mode);
66 		monitor_show_range(range);
67 	}
68 	else
69 	{
70 		char default_monitor[] = "generic_15";
71 
72 		memset(&range[0], 0, sizeof(struct monitor_range) * MAX_RANGES);
73 
74 		if (!strcmp(m_ds.monitor, "custom"))
75 			for (int i = 0; i < MAX_RANGES; i++) monitor_fill_range(&range[i], m_ds.crt_range[i]);
76 
77 		else if (!strcmp(m_ds.monitor, "lcd"))
78 			monitor_fill_lcd_range(&range[0], m_ds.lcd_range);
79 
80 		else if (monitor_set_preset(m_ds.monitor, range) == 0)
81 			monitor_set_preset(default_monitor, range);
82 	}
83 }
84 
85 //============================================================
86 //  display_manager::init
87 //============================================================
88 
init()89 bool display_manager::init()
90 {
91 	sprintf(m_ds.screen, "ram");
92 
93 	return true;
94 }
95 
96 //============================================================
97 //  display_manager::caps
98 //============================================================
99 
caps()100 int display_manager::caps()
101 {
102 	if (video())
103 		return video()->caps();
104 	else
105 		return CUSTOM_VIDEO_CAPS_ADD;
106 }
107 
108 //============================================================
109 //  display_manager::add_mode
110 //============================================================
111 
add_mode(modeline * mode)112 bool display_manager::add_mode(modeline *mode)
113 {
114 	if (video() == nullptr)
115 		return false;
116 
117 	// Add new mode
118 	if (!video()->add_mode(mode))
119 	{
120 		log_verbose("Switchres: error adding mode ");
121 		log_mode(mode);
122 		return false;
123 	}
124 
125 	mode->type &= ~MODE_ADD;
126 
127 	log_verbose("Switchres: added ");
128 	log_mode(mode);
129 
130 	return true;
131 }
132 
133 //============================================================
134 //  display_manager::delete_mode
135 //============================================================
136 
delete_mode(modeline * mode)137 bool display_manager::delete_mode(modeline *mode)
138 {
139 	if (video() == nullptr)
140 		return false;
141 
142 	if (!video()->delete_mode(mode))
143 	{
144 		log_verbose("Switchres: error deleting mode ");
145 		log_mode(mode);
146 		return false;
147 	}
148 
149 	log_verbose("Switchres: deleted ");
150 	log_mode(mode);
151 	return true;
152 }
153 
154 //============================================================
155 //  display_manager::update_mode
156 //============================================================
157 
update_mode(modeline * mode)158 bool display_manager::update_mode(modeline *mode)
159 {
160 	if (video() == nullptr)
161 		return false;
162 
163 	// Apply new timings
164 	if (!video()->update_mode(mode))
165 	{
166 		log_verbose("Switchres: error updating mode ");
167 		log_mode(mode);
168 		return false;
169 	}
170 
171 	mode->type &= ~MODE_UPDATE;
172 
173 	log_verbose("Switchres: updated ");
174 	log_mode(mode);
175 	return true;
176 }
177 
178 //============================================================
179 //  display_manager::set_mode
180 //============================================================
181 
set_mode(modeline *)182 bool display_manager::set_mode(modeline *)
183 {
184 	return false;
185 }
186 
187 //============================================================
188 //  display_manager::log_mode
189 //============================================================
190 
log_mode(modeline * mode)191 void display_manager::log_mode(modeline *mode)
192 {
193 	char modeline_txt[256];
194 	log_verbose("%s timing %s\n", video()->api_name(), modeline_print(mode, modeline_txt, MS_FULL));
195 }
196 
197 //============================================================
198 //  display_manager::restore_modes
199 //============================================================
200 
restore_modes()201 bool display_manager::restore_modes()
202 {
203 	// Compare each mode in our table with its original state
204 	for (unsigned i = video_modes.size(); i-- > 0; )
205 	{
206 		// First, delete all modes we've added
207 		if (i + 1 > backup_modes.size())
208 			video_modes[i].type |= MODE_DELETE;
209 
210 		// Now restore all modes which timings have been modified
211 		else if (modeline_is_different(&video_modes[i], &backup_modes[i]))
212 		{
213 			video_modes[i] = backup_modes[i];
214 			video_modes[i].type |= MODE_UPDATE;
215 		}
216 	}
217 	// Finally, flush pending changes to driver
218 	return flush_modes();
219 }
220 
221 //============================================================
222 //  display_manager::flush_modes
223 //============================================================
224 
flush_modes()225 bool display_manager::flush_modes()
226 {
227 	bool error = false;
228 	std::vector<modeline *> modified_modes = {};
229 
230 	if (video() == nullptr)
231 		return false;
232 
233 	// Loop through our mode table to collect all pending changes
234 	for (auto &mode : video_modes)
235 		if (mode.type & (MODE_UPDATE | MODE_ADD | MODE_DELETE))
236 			modified_modes.push_back(&mode);
237 
238 	// Flush pending changes to driver
239 	if (modified_modes.size() > 0)
240 	{
241 		video()->process_modelist(modified_modes);
242 
243 		// Log error/success result for each mode
244 		for (auto &mode : modified_modes)
245 		{
246 			log_verbose("Switchres: %s %s mode ", mode->type & MODE_ERROR? "error" : "success", mode->type & MODE_DELETE? "deleting" : mode->type & MODE_ADD? "adding" : "updating");
247 			log_mode(mode);
248 
249 			if (mode->type & MODE_ERROR)
250 				error = true;
251 		}
252 
253 		// Update our internal mode table to reflect the changes
254 		for (unsigned i = video_modes.size(); i-- > 0; )
255 		{
256 			if (video_modes[i].type & MODE_ERROR)
257 				continue;
258 
259 			if (video_modes[i].type & MODE_DELETE)
260 			{
261 				video_modes.erase(video_modes.begin() + i);
262 				m_best_mode = 0;
263 			}
264 			else
265 				video_modes[i].type &= ~(MODE_UPDATE | MODE_ADD);
266 		}
267 	}
268 
269 	return !error;
270 }
271 
272 //============================================================
273 //  display_manager::filter_modes
274 //============================================================
275 
filter_modes()276 bool display_manager::filter_modes()
277 {
278 	for (auto &mode : video_modes)
279 	{
280 		// apply options to mode type
281 		if (m_ds.refresh_dont_care)
282 			mode.type |= V_FREQ_EDITABLE;
283 
284 		if ((caps() & CUSTOM_VIDEO_CAPS_UPDATE))
285 			mode.type |= V_FREQ_EDITABLE;
286 
287 		if (caps() & CUSTOM_VIDEO_CAPS_SCAN_EDITABLE)
288 			mode.type |= SCAN_EDITABLE;
289 
290 		if (!m_ds.modeline_generation)
291 			mode.type &= ~(XYV_EDITABLE | SCAN_EDITABLE);
292 
293 		if ((mode.type & MODE_DESKTOP) && !(caps() & CUSTOM_VIDEO_CAPS_DESKTOP_EDITABLE))
294 			mode.type &= ~V_FREQ_EDITABLE;
295 
296 		if (m_ds.lock_system_modes && (mode.type & CUSTOM_VIDEO_TIMING_SYSTEM))
297 			mode.type |= MODE_DISABLED;
298 
299 		// Make sure to unlock the desktop mode as fallback
300 		if (mode.type & MODE_DESKTOP)
301 			mode.type &= ~MODE_DISABLED;
302 
303 		// Lock all modes that don't match the user's -resolution rules
304 		if (m_user_mode.width != 0 || m_user_mode.height != 0 || m_user_mode.refresh == !0)
305 		{
306 			if (!( (mode.width == m_user_mode.width || (mode.type & X_RES_EDITABLE) || m_user_mode.width == 0)
307 				&& (mode.height == m_user_mode.height || (mode.type & Y_RES_EDITABLE) || m_user_mode.height == 0)
308 				&& (mode.refresh == m_user_mode.refresh || (mode.type & V_FREQ_EDITABLE) || m_user_mode.refresh == 0) ))
309 				mode.type |= MODE_DISABLED;
310 			else
311 				mode.type &= ~MODE_DISABLED;
312 		}
313 	}
314 
315 	return true;
316 }
317 
318 //============================================================
319 //  display_manager::get_video_mode
320 //============================================================
321 
get_mode(int width,int height,float refresh,bool interlaced)322 modeline *display_manager::get_mode(int width, int height, float refresh, bool interlaced)
323 {
324 	modeline s_mode = {};
325 	modeline t_mode = {};
326 	modeline best_mode = {};
327 	char result[256]={'\x00'};
328 
329 	log_verbose("Switchres: Calculating best video mode for %dx%d@%.6f%s orientation: %s\n",
330 						width, height, refresh, interlaced?"i":"", rotation()?"rotated":"normal");
331 
332 	best_mode.result.weight |= R_OUT_OF_RANGE;
333 
334 	s_mode.interlace = interlaced;
335 	s_mode.vfreq = refresh;
336 
337 	s_mode.hactive = normalize(width, 8);
338 	s_mode.vactive = height;
339 
340 	if (rotation()) std::swap(s_mode.hactive, s_mode.vactive);
341 
342 	// Create a dummy mode entry if allowed
343 	if (caps() & CUSTOM_VIDEO_CAPS_ADD && m_ds.modeline_generation)
344 	{
345 		modeline new_mode = {};
346 		new_mode.type = XYV_EDITABLE | V_FREQ_EDITABLE | SCAN_EDITABLE | MODE_ADD | (desktop_is_rotated()? MODE_ROTATED : MODE_OK);
347 		video_modes.push_back(new_mode);
348 	}
349 
350 	// Run through our mode list and find the most suitable mode
351 	for (auto &mode : video_modes)
352 	{
353 		log_verbose("\nSwitchres: %s%4d%sx%s%4d%s_%s%d=%.6fHz%s%s\n",
354 			mode.type & X_RES_EDITABLE?"(":"[", mode.width, mode.type & X_RES_EDITABLE?")":"]",
355 			mode.type & Y_RES_EDITABLE?"(":"[", mode.height, mode.type & Y_RES_EDITABLE?")":"]",
356 			mode.type & V_FREQ_EDITABLE?"(":"[", mode.refresh, mode.vfreq, mode.type & V_FREQ_EDITABLE?")":"]",
357 			mode.type & MODE_DISABLED?" - locked":"");
358 
359 		// now get the mode if allowed
360 		if (!(mode.type & MODE_DISABLED))
361 		{
362 			for (int i = 0 ; i < MAX_RANGES ; i++)
363 			{
364 				if (range[i].hfreq_min)
365 				{
366 					t_mode = mode;
367 
368 					// init all editable fields with source or user values
369 					if (t_mode.type & X_RES_EDITABLE)
370 						t_mode.hactive = m_user_mode.width? m_user_mode.width : s_mode.hactive;
371 
372 					if (t_mode.type & Y_RES_EDITABLE)
373 						t_mode.vactive = m_user_mode.height? m_user_mode.height : s_mode.vactive;
374 
375 					if (t_mode.type & V_FREQ_EDITABLE)
376 					{
377 						// If user's vfreq is defined, it means we have an user modeline, so force it
378 						if (m_user_mode.vfreq)
379 							t_mode = m_user_mode;
380 						else
381 							t_mode.vfreq = s_mode.vfreq;
382 					}
383 
384 					// lock resolution fields if required
385 					if (m_user_mode.width) t_mode.type &= ~X_RES_EDITABLE;
386 					if (m_user_mode.height) t_mode.type &= ~Y_RES_EDITABLE;
387 					if (m_user_mode.vfreq) t_mode.type &= ~V_FREQ_EDITABLE;
388 
389 					modeline_create(&s_mode, &t_mode, &range[i], &m_ds.gs);
390 					t_mode.range = i;
391 
392 					log_verbose("%s\n", modeline_result(&t_mode, result));
393 
394 					if (modeline_compare(&t_mode, &best_mode))
395 					{
396 						best_mode = t_mode;
397 						m_best_mode = &mode;
398 					}
399 				}
400 			}
401 		}
402 	}
403 
404 	// If we didn't need to create a new mode, remove our dummy entry
405 	if (caps() & CUSTOM_VIDEO_CAPS_ADD && m_ds.modeline_generation && m_best_mode != &video_modes.back())
406 		video_modes.pop_back();
407 
408 	// If we didn't find a suitable mode, exit now
409 	if (best_mode.result.weight & R_OUT_OF_RANGE)
410 	{
411 		m_best_mode = 0;
412 		log_error("Switchres: could not find a video mode that meets your specs\n");
413 		return nullptr;
414 	}
415 
416 	log_verbose("\nSwitchres: %s (%dx%d@%.6f)->(%dx%d@%.6f)\n", rotation()?"rotated":"normal",
417 		width, height, refresh, best_mode.hactive, best_mode.vactive, best_mode.vfreq);
418 
419 	log_verbose("%s\n", modeline_result(&best_mode, result));
420 
421 	// Copy the new modeline to our mode list
422 	if (m_ds.modeline_generation)
423 	{
424 		if (best_mode.type & MODE_ADD)
425 		{
426 			best_mode.width = best_mode.hactive;
427 			best_mode.height = best_mode.vactive;
428 			best_mode.refresh = int(best_mode.vfreq);
429 			// lock new mode
430 			best_mode.type &= ~(X_RES_EDITABLE | Y_RES_EDITABLE | (caps() & CUSTOM_VIDEO_CAPS_UPDATE? 0 : V_FREQ_EDITABLE));
431 		}
432 		else if (modeline_is_different(&best_mode, m_best_mode) != 0)
433 			best_mode.type |= MODE_UPDATE;
434 
435 		char modeline[256]={'\x00'};
436 		log_info("Switchres: Modeline %s\n", modeline_print(&best_mode, modeline, MS_FULL));
437 	}
438 
439 	// Check if new best mode is different than previous one
440 	m_switching_required = (m_current_mode != m_best_mode || best_mode.type & MODE_UPDATE);
441 
442 	*m_best_mode = best_mode;
443 	return m_best_mode;
444 }
445 
446 //============================================================
447 //  display_manager::auto_specs
448 //============================================================
449 
auto_specs()450 bool display_manager::auto_specs()
451 {
452 	// Make sure we have a valid mode
453 	if (desktop_mode.width == 0 || desktop_mode.height == 0 || desktop_mode.refresh == 0)
454 	{
455 		log_error("Switchres: Invalid desktop mode %dx%d@%d\n", desktop_mode.width, desktop_mode.height, desktop_mode.refresh);
456 		return false;
457 	}
458 
459 	log_verbose("Switchres: Creating automatic specs for LCD based on %s\n", (desktop_mode.type & CUSTOM_VIDEO_TIMING_SYSTEM)? "VESA GTF" : "current timings");
460 
461 	// Make sure our current refresh is within range if set to auto
462 	if (!strcmp(m_ds.lcd_range, "auto"))
463 	{
464 		sprintf(m_ds.lcd_range, "%d-%d", desktop_mode.refresh - 1, desktop_mode.refresh + 1);
465 		monitor_fill_lcd_range(range, m_ds.lcd_range);
466 	}
467 
468 	// Create a working range with the best possible information
469 	if (desktop_mode.type & CUSTOM_VIDEO_TIMING_SYSTEM) modeline_vesa_gtf(&desktop_mode);
470 	modeline_to_monitor_range(range, &desktop_mode);
471 	monitor_show_range(range);
472 
473 	// Force our resolution to LCD's native one
474 	modeline user_mode = {};
475 	user_mode.width = desktop_mode.width;
476 	user_mode.height = desktop_mode.height;
477 	user_mode.refresh = desktop_mode.refresh;
478 	set_user_mode(&user_mode);
479 
480 	return true;
481 }
482