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