xref: /haiku/src/preferences/screen/ScreenMode.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
1 /*
2  * Copyright 2005, 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 
59 static int
60 compare_mode(const void* _mode1, const void* _mode2)
61 {
62 	display_mode *mode1 = (display_mode *)_mode1;
63 	display_mode *mode2 = (display_mode *)_mode2;
64 	combine_mode combine1, combine2;
65 	uint16 width1, width2, height1, height2;
66 
67 	combine1 = get_combine_mode(*mode1);
68 	combine2 = get_combine_mode(*mode2);
69 
70 	width1 = mode1->virtual_width;
71 	height1 = mode1->virtual_height;
72 	width2 = mode2->virtual_width;
73 	height2 = mode2->virtual_height;
74 
75 	if (combine1 == kCombineHorizontally)
76 		width1 /= 2;
77 	if (combine1 == kCombineVertically)
78 		height1 /= 2;
79 	if (combine2 == kCombineHorizontally)
80 		width2 /= 2;
81 	if (combine2 == kCombineVertically)
82 		height2 /= 2;
83 
84 	if (width1 != width2)
85 		return width1 - width2;
86 
87 	if (height1 != height2)
88 		return height1 - height2;
89 
90 	return (int)(10 * get_refresh_rate(*mode1)
91 		-  10 * get_refresh_rate(*mode2));
92 }
93 
94 
95 //	#pragma mark -
96 
97 
98 int32
99 screen_mode::BitsPerPixel() const
100 {
101 	switch (space) {
102 		case B_RGB32:	return 32;
103 		case B_RGB24:	return 24;
104 		case B_RGB16:	return 16;
105 		case B_RGB15:	return 15;
106 		case B_CMAP8:	return 8;
107 		default:		return 0;
108 	}
109 }
110 
111 
112 bool
113 screen_mode::operator==(const screen_mode &other) const
114 {
115 	return !(*this != other);
116 }
117 
118 
119 bool
120 screen_mode::operator!=(const screen_mode &other) const
121 {
122 	return width != other.width || height != other.height
123 		|| space != other.space || refresh != other.refresh
124 		|| combine != other.combine
125 		|| swap_displays != other.swap_displays
126 		|| use_laptop_panel != other.use_laptop_panel
127 		|| tv_standard != other.tv_standard;
128 }
129 
130 
131 void
132 screen_mode::SetTo(display_mode& mode)
133 {
134 	width = mode.virtual_width;
135 	height = mode.virtual_height;
136 	space = (color_space)mode.space;
137 	combine = get_combine_mode(mode);
138 	refresh = get_refresh_rate(mode);
139 
140 	if (combine == kCombineHorizontally)
141 		width /= 2;
142 	else if (combine == kCombineVertically)
143 		height /= 2;
144 
145 	swap_displays = false;
146 	use_laptop_panel = false;
147 	tv_standard = 0;
148 }
149 
150 
151 //	#pragma mark -
152 
153 
154 ScreenMode::ScreenMode(BWindow* window)
155 	:
156 	fWindow(window),
157 	fUpdatedModes(false)
158 {
159 	BScreen screen(window);
160 	if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) {
161 		// sort modes by resolution and refresh to make
162 		// the resolution and refresh menu look nicer
163 		qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode);
164 	} else {
165 		fModeList = NULL;
166 		fModeCount = 0;
167 	}
168 }
169 
170 
171 ScreenMode::~ScreenMode()
172 {
173 	free(fModeList);
174 }
175 
176 
177 status_t
178 ScreenMode::Set(const screen_mode& mode, int32 workspace)
179 {
180 	if (!fUpdatedModes)
181 		UpdateOriginalModes();
182 
183 	BScreen screen(fWindow);
184 
185 	if (workspace == ~0)
186 		workspace = current_workspace();
187 
188 	// TODO: our app_server doesn't fully support workspaces, yet
189 	SetSwapDisplays(&screen, mode.swap_displays);
190 	SetUseLaptopPanel(&screen, mode.use_laptop_panel);
191 	SetTVStandard(&screen, mode.tv_standard);
192 
193 	display_mode displayMode;
194 	if (!GetDisplayMode(mode, displayMode))
195 		return B_ENTRY_NOT_FOUND;
196 
197 	return screen.SetMode(workspace, &displayMode, true);
198 }
199 
200 
201 status_t
202 ScreenMode::Get(screen_mode& mode, int32 workspace) const
203 {
204 	display_mode displayMode;
205 	BScreen screen(fWindow);
206 
207 	if (workspace == ~0)
208 		workspace = current_workspace();
209 
210 	if (screen.GetMode(workspace, &displayMode) != B_OK)
211 		return B_ERROR;
212 
213 	mode.SetTo(displayMode);
214 
215 	// TODO: our app_server doesn't fully support workspaces, yet
216 	if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK)
217 		mode.swap_displays = false;
218 	if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK)
219 		mode.use_laptop_panel = false;
220 	if (GetTVStandard(&screen, &mode.tv_standard) != B_OK)
221 		mode.tv_standard = 0;
222 
223 	return B_OK;
224 }
225 
226 
227 status_t
228 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const
229 {
230 	if (workspace == ~0)
231 		workspace = current_workspace();
232 	else if(workspace > 31)
233 		return B_BAD_INDEX;
234 
235 	mode = fOriginal[workspace];
236 
237 	return B_OK;
238 }
239 
240 
241 // this method assumes that you already reverted to the correct number of workspaces
242 status_t
243 ScreenMode::Revert()
244 {
245 	if (!fUpdatedModes)
246 		return B_ERROR;
247 
248 	status_t result = B_OK;
249 	screen_mode current;
250 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
251 		if (Get(current, workspace) == B_OK && fOriginal[workspace] == current)
252 			continue;
253 
254 		BScreen screen(fWindow);
255 
256 		// TODO: our app_server doesn't fully support workspaces, yet
257 		if (workspace == current_workspace()) {
258 			SetSwapDisplays(&screen, fOriginal[workspace].swap_displays);
259 			SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel);
260 			SetTVStandard(&screen, fOriginal[workspace].tv_standard);
261 		}
262 
263 		result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace], true);
264 		if (result != B_OK)
265 			break;
266 	}
267 
268 	return result;
269 }
270 
271 
272 void
273 ScreenMode::UpdateOriginalModes()
274 {
275 	BScreen screen(fWindow);
276 	for (int32 workspace = 0; workspace < count_workspaces(); workspace++) {
277 		if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace]) == B_OK) {
278 			Get(fOriginal[workspace], workspace);
279 			fUpdatedModes = true;
280 		}
281 	}
282 }
283 
284 
285 bool
286 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space)
287 {
288 	return true;
289 }
290 
291 
292 status_t
293 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max)
294 {
295 	uint32 minClock, maxClock;
296 	display_mode displayMode;
297 	if (!GetDisplayMode(mode, displayMode))
298 		return B_ERROR;
299 
300 	BScreen screen(fWindow);
301 	if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK)
302 		return B_ERROR;
303 
304 	uint32 total = displayMode.timing.h_total * displayMode.timing.v_total;
305 	min = minClock * 1000.0 / total;
306 	max = maxClock * 1000.0 / total;
307 
308 	return B_OK;
309 }
310 
311 
312 screen_mode
313 ScreenMode::ModeAt(int32 index)
314 {
315 	if (index < 0)
316 		index = 0;
317 	else if (index >= (int32)fModeCount)
318 		index = fModeCount - 1;
319 
320 	screen_mode mode;
321 	mode.SetTo(fModeList[index]);
322 
323 	return mode;
324 }
325 
326 
327 int32
328 ScreenMode::CountModes()
329 {
330 	return fModeCount;
331 }
332 
333 
334 bool
335 ScreenMode::GetDisplayMode(const screen_mode& mode, display_mode& displayMode)
336 {
337 	uint16 virtualWidth, virtualHeight;
338 	int32 bestIndex = -1;
339 	float bestDiff = 999;
340 
341 	virtualWidth = mode.combine == kCombineHorizontally ?
342 		mode.width * 2 : mode.width;
343 	virtualHeight = mode.combine == kCombineVertically ?
344 		mode.height * 2 : mode.height;
345 
346 	// try to find mode in list provided by driver
347 	for (uint32 i = 0; i < fModeCount; i++) {
348 		if (fModeList[i].virtual_width != virtualWidth
349 			|| fModeList[i].virtual_height != virtualHeight
350 			|| (color_space)fModeList[i].space != mode.space)
351 			continue;
352 
353 		float refresh = get_refresh_rate(fModeList[i]);
354 		if (refresh == mode.refresh) {
355 			// we have luck - we can use this mode directly
356 			displayMode = fModeList[i];
357 			displayMode.h_display_start = 0;
358 			displayMode.v_display_start = 0;
359 			return true;
360 		}
361 
362 		float diff = fabs(refresh - mode.refresh);
363 		if (diff < bestDiff) {
364 			bestDiff = diff;
365 			bestIndex = i;
366 		}
367 	}
368 
369 	// we didn't find the exact mode, but something very similar?
370 	if (bestIndex == -1)
371 		return false;
372 
373 	// now, we are better of using GMT formula, but
374 	// as we don't have it, we just tune the pixel
375 	// clock of the best mode.
376 
377 	displayMode = fModeList[bestIndex];
378 	displayMode.h_display_start = 0;
379 	displayMode.v_display_start = 0;
380 
381 	// after some fiddling, it looks like this is the formula
382 	// used by the original panel (notice that / 10 happens before
383 	// multiplying with refresh rate - this leads to different
384 	// rounding)
385 	displayMode.timing.pixel_clock = ((uint32)displayMode.timing.h_total
386 		* displayMode.timing.v_total / 10 * int32(mode.refresh * 10)) / 1000;
387 
388 	return true;
389 }
390 
391