xref: /haiku/src/preferences/screen/ScreenMode.cpp (revision d2e1e872611179c9cfaa43ce11bd58b1e3554e4b)
1 /*
2  * Copyright 2005-2008, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include "ScreenMode.h"
11 
12 #include <InterfaceDefs.h>
13 
14 #include <stdlib.h>
15 #include <stdio.h>
16 
17 
18 /* Note, this headers defines a *private* interface to the Radeon accelerant.
19  * It's a solution that works with the current BeOS interface that Haiku
20  * adopted.
21  * However, it's not a nice and clean solution. Don't use this header in any
22  * application if you can avoid it. No other driver is using this, or should
23  * be using this.
24  * It will be replaced as soon as we introduce an updated accelerant interface
25  * which may even happen before R1 hits the streets.
26  */
27 
28 #include "multimon.h"	// the usual: DANGER WILL, ROBINSON!
29 
30 
31 static combine_mode
32 get_combine_mode(display_mode& mode)
33 {
34 	if (mode.flags & B_SCROLL == 0)
35 		return kCombineDisable;
36 
37 	if (mode.virtual_width == mode.timing.h_display * 2)
38 		return kCombineHorizontally;
39 
40 	if (mode.virtual_height == mode.timing.v_display * 2)
41 		return kCombineVertically;
42 
43 	return kCombineDisable;
44 }
45 
46 
47 static float
48 get_refresh_rate(display_mode& mode)
49 {
50 	// we have to be catious as refresh rate cannot be controlled directly,
51 	// so it suffers under rounding errors and hardware restrictions
52 	return rint(10 * float(mode.timing.pixel_clock * 1000) /
53 		float(mode.timing.h_total * mode.timing.v_total)) / 10.0;
54 }
55 
56 
57 /*!	Helper to sort modes by resolution */
58 static int
59 compare_mode(const void* _mode1, const void* _mode2)
60 {
61 	display_mode *mode1 = (display_mode *)_mode1;
62 	display_mode *mode2 = (display_mode *)_mode2;
63 	combine_mode combine1, combine2;
64 	uint16 width1, width2, height1, height2;
65 
66 	combine1 = get_combine_mode(*mode1);
67 	combine2 = get_combine_mode(*mode2);
68 
69 	width1 = mode1->virtual_width;
70 	height1 = mode1->virtual_height;
71 	width2 = mode2->virtual_width;
72 	height2 = mode2->virtual_height;
73 
74 	if (combine1 == kCombineHorizontally)
75 		width1 /= 2;
76 	if (combine1 == kCombineVertically)
77 		height1 /= 2;
78 	if (combine2 == kCombineHorizontally)
79 		width2 /= 2;
80 	if (combine2 == kCombineVertically)
81 		height2 /= 2;
82 
83 	if (width1 != width2)
84 		return width1 - width2;
85 
86 	if (height1 != height2)
87 		return height1 - height2;
88 
89 	return (int)(10 * get_refresh_rate(*mode1)
90 		-  10 * get_refresh_rate(*mode2));
91 }
92 
93 
94 //	#pragma mark -
95 
96 
97 int32
98 screen_mode::BitsPerPixel() const
99 {
100 	switch (space) {
101 		case B_RGB32:	return 32;
102 		case B_RGB24:	return 24;
103 		case B_RGB16:	return 16;
104 		case B_RGB15:	return 15;
105 		case B_CMAP8:	return 8;
106 		default:		return 0;
107 	}
108 }
109 
110 
111 bool
112 screen_mode::operator==(const screen_mode &other) const
113 {
114 	return !(*this != other);
115 }
116 
117 
118 bool
119 screen_mode::operator!=(const screen_mode &other) const
120 {
121 	// make no difference between 24 and 32 bit modes
122 	color_space thisSpace = space;
123 	if (thisSpace == B_RGB24)
124 		thisSpace = B_RGB32;
125 
126 	color_space otherSpace = other.space;
127 	if (otherSpace == B_RGB24)
128 		otherSpace = B_RGB32;
129 
130 	return width != other.width || height != other.height
131 		|| thisSpace != otherSpace || refresh != other.refresh
132 		|| combine != other.combine
133 		|| swap_displays != other.swap_displays
134 		|| use_laptop_panel != other.use_laptop_panel
135 		|| tv_standard != other.tv_standard;
136 }
137 
138 
139 void
140 screen_mode::SetTo(display_mode& mode)
141 {
142 	width = mode.virtual_width;
143 	height = mode.virtual_height;
144 	space = (color_space)mode.space;
145 	combine = get_combine_mode(mode);
146 	refresh = get_refresh_rate(mode);
147 
148 	if (combine == kCombineHorizontally)
149 		width /= 2;
150 	else if (combine == kCombineVertically)
151 		height /= 2;
152 
153 	swap_displays = false;
154 	use_laptop_panel = false;
155 	tv_standard = 0;
156 }
157 
158 
159 //	#pragma mark -
160 
161 
162 ScreenMode::ScreenMode(BWindow* window)
163 	:
164 	fWindow(window),
165 	fUpdatedModes(false)
166 {
167 	BScreen screen(window);
168 	if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) {
169 		// sort modes by resolution and refresh to make
170 		// the resolution and refresh menu look nicer
171 		qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode);
172 	} else {
173 		fModeList = NULL;
174 		fModeCount = 0;
175 	}
176 }
177 
178 
179 ScreenMode::~ScreenMode()
180 {
181 	free(fModeList);
182 }
183 
184 
185 status_t
186 ScreenMode::Set(const screen_mode& mode, int32 workspace)
187 {
188 	if (!fUpdatedModes)
189 		UpdateOriginalModes();
190 
191 	BScreen screen(fWindow);
192 
193 	if (workspace == ~0)
194 		workspace = current_workspace();
195 
196 	// TODO: our app_server doesn't fully support workspaces, yet
197 	SetSwapDisplays(&screen, mode.swap_displays);
198 	SetUseLaptopPanel(&screen, mode.use_laptop_panel);
199 	SetTVStandard(&screen, mode.tv_standard);
200 
201 	display_mode displayMode;
202 	if (!GetDisplayMode(mode, displayMode))
203 		return B_ENTRY_NOT_FOUND;
204 
205 	return screen.SetMode(workspace, &displayMode, true);
206 }
207 
208 
209 status_t
210 ScreenMode::Get(screen_mode& mode, int32 workspace) const
211 {
212 	display_mode displayMode;
213 	BScreen screen(fWindow);
214 
215 	if (workspace == ~0)
216 		workspace = current_workspace();
217 
218 	if (screen.GetMode(workspace, &displayMode) != B_OK)
219 		return B_ERROR;
220 
221 	mode.SetTo(displayMode);
222 
223 	// TODO: our app_server doesn't fully support workspaces, yet
224 	if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK)
225 		mode.swap_displays = false;
226 	if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK)
227 		mode.use_laptop_panel = false;
228 	if (GetTVStandard(&screen, &mode.tv_standard) != B_OK)
229 		mode.tv_standard = 0;
230 
231 	return B_OK;
232 }
233 
234 
235 status_t
236 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const
237 {
238 	if (workspace == ~0)
239 		workspace = current_workspace();
240 	else if(workspace > 31)
241 		return B_BAD_INDEX;
242 
243 	mode = fOriginal[workspace];
244 
245 	return B_OK;
246 }
247 
248 
249 /*!	This method assumes that you already reverted to the correct number
250 	of workspaces.
251 */
252 status_t
253 ScreenMode::Revert()
254 {
255 	if (!fUpdatedModes)
256 		return B_ERROR;
257 
258 	status_t result = B_OK;
259 	screen_mode current;
260 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
261 		if (Get(current, workspace) == B_OK && fOriginal[workspace] == current)
262 			continue;
263 
264 		BScreen screen(fWindow);
265 
266 		// TODO: our app_server doesn't fully support workspaces, yet
267 		if (workspace == current_workspace()) {
268 			SetSwapDisplays(&screen, fOriginal[workspace].swap_displays);
269 			SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel);
270 			SetTVStandard(&screen, fOriginal[workspace].tv_standard);
271 		}
272 
273 		result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace],
274 			true);
275 		if (result != B_OK)
276 			break;
277 	}
278 
279 	return result;
280 }
281 
282 
283 void
284 ScreenMode::UpdateOriginalModes()
285 {
286 	BScreen screen(fWindow);
287 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
288 		if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace])
289 				== B_OK) {
290 			Get(fOriginal[workspace], workspace);
291 			fUpdatedModes = true;
292 		}
293 	}
294 }
295 
296 
297 bool
298 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
299 {
300 	return true;
301 }
302 
303 
304 status_t
305 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max)
306 {
307 	uint32 minClock, maxClock;
308 	display_mode displayMode;
309 	if (!GetDisplayMode(mode, displayMode))
310 		return B_ERROR;
311 
312 	BScreen screen(fWindow);
313 	if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK)
314 		return B_ERROR;
315 
316 	uint32 total = displayMode.timing.h_total * displayMode.timing.v_total;
317 	min = minClock * 1000.0 / total;
318 	max = maxClock * 1000.0 / total;
319 
320 	return B_OK;
321 }
322 
323 
324 screen_mode
325 ScreenMode::ModeAt(int32 index)
326 {
327 	if (index < 0)
328 		index = 0;
329 	else if (index >= (int32)fModeCount)
330 		index = fModeCount - 1;
331 
332 	screen_mode mode;
333 	mode.SetTo(fModeList[index]);
334 
335 	return mode;
336 }
337 
338 
339 int32
340 ScreenMode::CountModes()
341 {
342 	return fModeCount;
343 }
344 
345 
346 bool
347 ScreenMode::GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
348 {
349 	uint16 virtualWidth, virtualHeight;
350 	int32 bestIndex = -1;
351 	float bestDiff = 999;
352 
353 	virtualWidth = mode.combine == kCombineHorizontally ?
354 		mode.width * 2 : mode.width;
355 	virtualHeight = mode.combine == kCombineVertically ?
356 		mode.height * 2 : mode.height;
357 
358 	// try to find mode in list provided by driver
359 	for (uint32 i = 0; i < fModeCount; i++) {
360 		if (fModeList[i].virtual_width != virtualWidth
361 			|| fModeList[i].virtual_height != virtualHeight
362 			|| (color_space)fModeList[i].space != mode.space)
363 			continue;
364 
365 		float refresh = get_refresh_rate(fModeList[i]);
366 		if (refresh == mode.refresh) {
367 			// we have luck - we can use this mode directly
368 			displayMode = fModeList[i];
369 			displayMode.h_display_start = 0;
370 			displayMode.v_display_start = 0;
371 			return true;
372 		}
373 
374 		float diff = fabs(refresh - mode.refresh);
375 		if (diff < bestDiff) {
376 			bestDiff = diff;
377 			bestIndex = i;
378 		}
379 	}
380 
381 	// we didn't find the exact mode, but something very similar?
382 	if (bestIndex == -1)
383 		return false;
384 
385 	// now, we are better of using GMT formula, but
386 	// as we don't have it, we just tune the pixel
387 	// clock of the best mode.
388 
389 	displayMode = fModeList[bestIndex];
390 	displayMode.h_display_start = 0;
391 	displayMode.v_display_start = 0;
392 
393 	// after some fiddling, it looks like this is the formula
394 	// used by the original panel (notice that / 10 happens before
395 	// multiplying with refresh rate - this leads to different
396 	// rounding)
397 	displayMode.timing.pixel_clock = ((uint32)displayMode.timing.h_total
398 		* displayMode.timing.v_total / 10 * int32(mode.refresh * 10)) / 1000;
399 
400 	return true;
401 }
402 
403