1 /* 2 * Copyright 2005-2011, 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 <stdlib.h> 13 #include <stdio.h> 14 #include <string.h> 15 16 #include <algorithm> 17 18 #include <InterfaceDefs.h> 19 #include <String.h> 20 21 #include <compute_display_timing.h> 22 23 24 /* Note, this headers defines a *private* interface to the Radeon accelerant. 25 * It's a solution that works with the current BeOS interface that Haiku 26 * adopted. 27 * However, it's not a nice and clean solution. Don't use this header in any 28 * application if you can avoid it. No other driver is using this, or should 29 * be using this. 30 * It will be replaced as soon as we introduce an updated accelerant interface 31 * which may even happen before R1 hits the streets. 32 */ 33 34 #include "multimon.h" // the usual: DANGER WILL, ROBINSON! 35 36 37 38 // Vendors.h is generated using these commands (plus a bit of manual editing): 39 /* 40 * wget https://uefi.org/uefi-pnp-export -O Vendors.h.tmp 41 * if [ $? -eq 0 ]; then 42 * sed -E -e 's:<thead>::g' -e 's:<tr.*="(.+)"><td>:{ ":g' -e 's:<td>[[:digit:]]{2}/[[:digit:]]{2}/[[:digit:]]{4}</td>::g' -e 's: *(<\/td><td>):\", \":g' -e 's: *<\/td>.<\/tr>:\" },:g' -e 's/"(.*?)", "(.*?)"/\"\2\", \"\1\"/' -e 's/&/\&/' -e "s/'/'/" Vendors.h.tmp | grep -v '<' | sort > Vendors.h 43 * fi 44 * rm Vendors.h.tmp 45 */ 46 47 struct pnp_id { 48 const char* id; 49 const char* manufacturer; 50 bool operator==(const pnp_id& a) const { 51 return ::strcasecmp(a.id, id) == 0; 52 }; 53 bool operator()(const pnp_id& a, const pnp_id& b) const { 54 return ::strcasecmp(a.id, b.id) < 0; 55 }; 56 }; 57 58 static const struct pnp_id kPNPIDs[] = { 59 #include "Vendors.h" 60 }; 61 62 63 static combine_mode 64 get_combine_mode(display_mode& mode) 65 { 66 if ((mode.flags & B_SCROLL) == 0) 67 return kCombineDisable; 68 69 if (mode.virtual_width == mode.timing.h_display * 2) 70 return kCombineHorizontally; 71 72 if (mode.virtual_height == mode.timing.v_display * 2) 73 return kCombineVertically; 74 75 return kCombineDisable; 76 } 77 78 79 static float 80 get_refresh_rate(display_mode& mode) 81 { 82 // we have to be catious as refresh rate cannot be controlled directly, 83 // so it suffers under rounding errors and hardware restrictions 84 return rint(10 * float(mode.timing.pixel_clock * 1000) 85 / float(mode.timing.h_total * mode.timing.v_total)) / 10.0; 86 } 87 88 89 /*! Helper to sort modes by resolution */ 90 static int 91 compare_mode(const void* _mode1, const void* _mode2) 92 { 93 display_mode *mode1 = (display_mode *)_mode1; 94 display_mode *mode2 = (display_mode *)_mode2; 95 combine_mode combine1, combine2; 96 uint16 width1, width2, height1, height2; 97 98 combine1 = get_combine_mode(*mode1); 99 combine2 = get_combine_mode(*mode2); 100 101 width1 = mode1->virtual_width; 102 height1 = mode1->virtual_height; 103 width2 = mode2->virtual_width; 104 height2 = mode2->virtual_height; 105 106 if (combine1 == kCombineHorizontally) 107 width1 /= 2; 108 if (combine1 == kCombineVertically) 109 height1 /= 2; 110 if (combine2 == kCombineHorizontally) 111 width2 /= 2; 112 if (combine2 == kCombineVertically) 113 height2 /= 2; 114 115 if (width1 != width2) 116 return width1 - width2; 117 118 if (height1 != height2) 119 return height1 - height2; 120 121 return (int)(10 * get_refresh_rate(*mode1) 122 - 10 * get_refresh_rate(*mode2)); 123 } 124 125 126 // #pragma mark - 127 128 129 int32 130 screen_mode::BitsPerPixel() const 131 { 132 switch (space) { 133 case B_RGB32: return 32; 134 case B_RGB24: return 24; 135 case B_RGB16: return 16; 136 case B_RGB15: return 15; 137 case B_CMAP8: return 8; 138 default: return 0; 139 } 140 } 141 142 143 bool 144 screen_mode::operator==(const screen_mode &other) const 145 { 146 return !(*this != other); 147 } 148 149 150 bool 151 screen_mode::operator!=(const screen_mode &other) const 152 { 153 return width != other.width || height != other.height 154 || space != other.space || refresh != other.refresh 155 || combine != other.combine 156 || swap_displays != other.swap_displays 157 || use_laptop_panel != other.use_laptop_panel 158 || tv_standard != other.tv_standard; 159 } 160 161 162 void 163 screen_mode::SetTo(display_mode& mode) 164 { 165 width = mode.virtual_width; 166 height = mode.virtual_height; 167 space = (color_space)mode.space; 168 combine = get_combine_mode(mode); 169 refresh = get_refresh_rate(mode); 170 171 if (combine == kCombineHorizontally) 172 width /= 2; 173 else if (combine == kCombineVertically) 174 height /= 2; 175 176 swap_displays = false; 177 use_laptop_panel = false; 178 tv_standard = 0; 179 } 180 181 182 // #pragma mark - 183 184 185 ScreenMode::ScreenMode(BWindow* window) 186 : 187 fWindow(window), 188 fUpdatedModes(false) 189 { 190 BScreen screen(window); 191 if (screen.GetModeList(&fModeList, &fModeCount) == B_OK) { 192 // sort modes by resolution and refresh to make 193 // the resolution and refresh menu look nicer 194 qsort(fModeList, fModeCount, sizeof(display_mode), compare_mode); 195 } else { 196 fModeList = NULL; 197 fModeCount = 0; 198 } 199 } 200 201 202 ScreenMode::~ScreenMode() 203 { 204 free(fModeList); 205 } 206 207 208 status_t 209 ScreenMode::Set(const screen_mode& mode, int32 workspace) 210 { 211 if (!fUpdatedModes) 212 UpdateOriginalModes(); 213 214 BScreen screen(fWindow); 215 216 if (workspace == ~0) 217 workspace = current_workspace(); 218 219 // TODO: our app_server doesn't fully support workspaces, yet 220 SetSwapDisplays(&screen, mode.swap_displays); 221 SetUseLaptopPanel(&screen, mode.use_laptop_panel); 222 SetTVStandard(&screen, mode.tv_standard); 223 224 display_mode displayMode; 225 if (!_GetDisplayMode(mode, displayMode)) 226 return B_ENTRY_NOT_FOUND; 227 228 return screen.SetMode(workspace, &displayMode, true); 229 } 230 231 232 status_t 233 ScreenMode::Get(screen_mode& mode, int32 workspace) const 234 { 235 display_mode displayMode; 236 BScreen screen(fWindow); 237 238 if (workspace == ~0) 239 workspace = current_workspace(); 240 241 if (screen.GetMode(workspace, &displayMode) != B_OK) 242 return B_ERROR; 243 244 mode.SetTo(displayMode); 245 246 // TODO: our app_server doesn't fully support workspaces, yet 247 if (GetSwapDisplays(&screen, &mode.swap_displays) != B_OK) 248 mode.swap_displays = false; 249 if (GetUseLaptopPanel(&screen, &mode.use_laptop_panel) != B_OK) 250 mode.use_laptop_panel = false; 251 if (GetTVStandard(&screen, &mode.tv_standard) != B_OK) 252 mode.tv_standard = 0; 253 254 return B_OK; 255 } 256 257 258 status_t 259 ScreenMode::GetOriginalMode(screen_mode& mode, int32 workspace) const 260 { 261 if (workspace == ~0) 262 workspace = current_workspace(); 263 // TODO this should use kMaxWorkspaces 264 else if (workspace > 31) 265 return B_BAD_INDEX; 266 267 mode = fOriginal[workspace]; 268 269 return B_OK; 270 } 271 272 273 status_t 274 ScreenMode::Set(const display_mode& mode, int32 workspace) 275 { 276 if (!fUpdatedModes) 277 UpdateOriginalModes(); 278 279 BScreen screen(fWindow); 280 281 if (workspace == ~0) 282 workspace = current_workspace(); 283 284 // BScreen::SetMode() needs a non-const display_mode 285 display_mode nonConstMode; 286 memcpy(&nonConstMode, &mode, sizeof(display_mode)); 287 return screen.SetMode(workspace, &nonConstMode, true); 288 } 289 290 291 status_t 292 ScreenMode::Get(display_mode& mode, int32 workspace) const 293 { 294 BScreen screen(fWindow); 295 296 if (workspace == ~0) 297 workspace = current_workspace(); 298 299 return screen.GetMode(workspace, &mode); 300 } 301 302 303 /*! This method assumes that you already reverted to the correct number 304 of workspaces. 305 */ 306 status_t 307 ScreenMode::Revert() 308 { 309 if (!fUpdatedModes) 310 return B_ERROR; 311 312 status_t result = B_OK; 313 screen_mode current; 314 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { 315 if (Get(current, workspace) == B_OK && fOriginal[workspace] == current) 316 continue; 317 318 BScreen screen(fWindow); 319 320 // TODO: our app_server doesn't fully support workspaces, yet 321 if (workspace == current_workspace()) { 322 SetSwapDisplays(&screen, fOriginal[workspace].swap_displays); 323 SetUseLaptopPanel(&screen, fOriginal[workspace].use_laptop_panel); 324 SetTVStandard(&screen, fOriginal[workspace].tv_standard); 325 } 326 327 result = screen.SetMode(workspace, &fOriginalDisplayMode[workspace], 328 true); 329 if (result != B_OK) 330 break; 331 } 332 333 return result; 334 } 335 336 337 void 338 ScreenMode::UpdateOriginalModes() 339 { 340 BScreen screen(fWindow); 341 for (int32 workspace = 0; workspace < count_workspaces(); workspace++) { 342 if (screen.GetMode(workspace, &fOriginalDisplayMode[workspace]) 343 == B_OK) { 344 Get(fOriginal[workspace], workspace); 345 fUpdatedModes = true; 346 } 347 } 348 } 349 350 351 bool 352 ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space) 353 { 354 return true; 355 } 356 357 358 status_t 359 ScreenMode::GetRefreshLimits(const screen_mode& mode, float& min, float& max) 360 { 361 uint32 minClock, maxClock; 362 display_mode displayMode; 363 if (!_GetDisplayMode(mode, displayMode)) 364 return B_ERROR; 365 366 BScreen screen(fWindow); 367 if (screen.GetPixelClockLimits(&displayMode, &minClock, &maxClock) < B_OK) 368 return B_ERROR; 369 370 uint32 total = displayMode.timing.h_total * displayMode.timing.v_total; 371 min = minClock * 1000.0 / total; 372 max = maxClock * 1000.0 / total; 373 374 return B_OK; 375 } 376 377 378 const char* 379 ScreenMode::GetManufacturerFromID(const char* id) const 380 { 381 // We assume the array is sorted 382 const size_t numElements = sizeof(kPNPIDs) / sizeof(kPNPIDs[0]); 383 const struct pnp_id key = { id, "dummy" }; 384 const pnp_id* lastElement = kPNPIDs + numElements; 385 const pnp_id* element = std::find(kPNPIDs, lastElement, key); 386 if (element == lastElement) { 387 // can't find the vendor code 388 return NULL; 389 } 390 391 return element->manufacturer; 392 } 393 394 395 status_t 396 ScreenMode::GetMonitorInfo(monitor_info& info, float* _diagonalInches) 397 { 398 BScreen screen(fWindow); 399 status_t status = screen.GetMonitorInfo(&info); 400 if (status != B_OK) 401 return status; 402 403 if (_diagonalInches != NULL) { 404 *_diagonalInches = round(sqrt(info.width * info.width 405 + info.height * info.height) / 0.254) / 10.0; 406 } 407 408 // Some older CRT monitors do not contain the monitor range information 409 // (EDID1_MONITOR_RANGES) in their EDID info resulting in the min/max 410 // horizontal/vertical frequencies being zero. In this case, set the 411 // vertical frequency range to 60..85 Hz. 412 if (info.min_vertical_frequency == 0) { 413 info.min_vertical_frequency = 60; 414 info.max_vertical_frequency = 85; 415 } 416 417 char vendor[4]; 418 strlcpy(vendor, info.vendor, sizeof(vendor)); 419 const char* vendorString = GetManufacturerFromID(vendor); 420 if (vendorString != NULL) 421 strlcpy(info.vendor, vendorString, sizeof(info.vendor)); 422 423 // Remove extraneous vendor strings and whitespace 424 425 BString name(info.name); 426 name.IReplaceAll(info.vendor, ""); 427 name.Trim(); 428 429 strcpy(info.name, name.String()); 430 431 return B_OK; 432 } 433 434 435 status_t 436 ScreenMode::GetDeviceInfo(accelerant_device_info& info) 437 { 438 BScreen screen(fWindow); 439 return screen.GetDeviceInfo(&info); 440 } 441 442 443 screen_mode 444 ScreenMode::ModeAt(int32 index) 445 { 446 if (index < 0) 447 index = 0; 448 else if (index >= (int32)fModeCount) 449 index = fModeCount - 1; 450 451 screen_mode mode; 452 mode.SetTo(fModeList[index]); 453 454 return mode; 455 } 456 457 458 const display_mode& 459 ScreenMode::DisplayModeAt(int32 index) 460 { 461 if (index < 0) 462 index = 0; 463 else if (index >= (int32)fModeCount) 464 index = fModeCount - 1; 465 466 return fModeList[index]; 467 } 468 469 470 int32 471 ScreenMode::CountModes() 472 { 473 return fModeCount; 474 } 475 476 477 /*! Searches for a similar mode in the reported mode list, and if that does not 478 find a matching mode, it will compute the mode manually using the GTF. 479 */ 480 bool 481 ScreenMode::_GetDisplayMode(const screen_mode& mode, display_mode& displayMode) 482 { 483 uint16 virtualWidth, virtualHeight; 484 int32 bestIndex = -1; 485 float bestDiff = 999; 486 487 virtualWidth = mode.combine == kCombineHorizontally 488 ? mode.width * 2 : mode.width; 489 virtualHeight = mode.combine == kCombineVertically 490 ? mode.height * 2 : mode.height; 491 492 // try to find mode in list provided by driver 493 for (uint32 i = 0; i < fModeCount; i++) { 494 if (fModeList[i].virtual_width != virtualWidth 495 || fModeList[i].virtual_height != virtualHeight 496 || (color_space)fModeList[i].space != mode.space) 497 continue; 498 499 // Accept the mode if the computed refresh rate of the mode is within 500 // 0.6 percent of the refresh rate specified by the caller. Note that 501 // refresh rates computed from mode parameters is not exact; especially 502 // some of the older modes such as 640x480, 800x600, and 1024x768. 503 // The tolerance of 0.6% was obtained by examining the various possible 504 // modes. 505 506 float refreshDiff = fabs(get_refresh_rate(fModeList[i]) - mode.refresh); 507 if (refreshDiff < 0.006 * mode.refresh) { 508 // Accept this mode. 509 displayMode = fModeList[i]; 510 displayMode.h_display_start = 0; 511 displayMode.v_display_start = 0; 512 513 // Since the computed refresh rate of the selected mode might differ 514 // from selected refresh rate by a few tenths (e.g. 60.2 instead of 515 // 60.0), tweak the pixel clock so the the refresh rate of the mode 516 // matches the selected refresh rate. 517 518 displayMode.timing.pixel_clock = uint32(((displayMode.timing.h_total 519 * displayMode.timing.v_total * mode.refresh) / 1000.0) + 0.5); 520 return true; 521 } 522 523 // Mode not acceptable. 524 525 if (refreshDiff < bestDiff) { 526 bestDiff = refreshDiff; 527 bestIndex = i; 528 } 529 } 530 531 // we didn't find the exact mode, but something very similar? 532 if (bestIndex == -1) 533 return false; 534 535 displayMode = fModeList[bestIndex]; 536 displayMode.h_display_start = 0; 537 displayMode.v_display_start = 0; 538 539 // For the mode selected by the width, height, and refresh rate, compute 540 // the video timing parameters for the mode by using the VESA Generalized 541 // Timing Formula (GTF). 542 compute_display_timing(mode.width, mode.height, mode.refresh, false, 543 &displayMode.timing); 544 545 return true; 546 } 547