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 47struct 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 58static const struct pnp_id kPNPIDs[] = { 59#include "Vendors.h" 60}; 61 62 63static combine_mode 64get_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 79static float 80get_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 */ 90static int 91compare_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 129int32 130screen_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 143bool 144screen_mode::operator==(const screen_mode &other) const 145{ 146 return !(*this != other); 147} 148 149 150bool 151screen_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 162void 163screen_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 185ScreenMode::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 202ScreenMode::~ScreenMode() 203{ 204 free(fModeList); 205} 206 207 208status_t 209ScreenMode::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 232status_t 233ScreenMode::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 258status_t 259ScreenMode::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 273status_t 274ScreenMode::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 291status_t 292ScreenMode::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*/ 306status_t 307ScreenMode::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 337void 338ScreenMode::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 351bool 352ScreenMode::SupportsColorSpace(const screen_mode& mode, color_space space) 353{ 354 return true; 355} 356 357 358status_t 359ScreenMode::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 378const char* 379ScreenMode::GetManufacturerFromID(const char* id) const 380{ 381 // We assume the array is sorted 382 const size_t numElements = B_COUNT_OF(kPNPIDs); 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 395status_t 396ScreenMode::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 435status_t 436ScreenMode::GetDeviceInfo(accelerant_device_info& info) 437{ 438 BScreen screen(fWindow); 439 return screen.GetDeviceInfo(&info); 440} 441 442 443screen_mode 444ScreenMode::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 458const display_mode& 459ScreenMode::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 470int32 471ScreenMode::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*/ 480bool 481ScreenMode::_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