1 /* 2 * Copyright 2001-2008, Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "DrawingEngine.h" 10 11 #include <Bitmap.h> 12 #include <stdio.h> 13 #include <algorithm> 14 #include <stack> 15 16 #include "DrawState.h" 17 #include "GlyphLayoutEngine.h" 18 #include "Painter.h" 19 #include "ServerBitmap.h" 20 #include "ServerCursor.h" 21 #include "RenderingBuffer.h" 22 23 #include "drawing_support.h" 24 25 #define CRASH_IF_NOT_LOCKED 26 //#define CRASH_IF_NOT_LOCKED if (!IsParallelAccessLocked()) debugger("not parallel locked!"); 27 28 #define CRASH_IF_NOT_EXCLUSIVE_LOCKED 29 //#define CRASH_IF_NOT_EXCLUSIVE_LOCKED if (!IsExclusiveAccessLocked()) debugger("not exclusive locked!"); 30 31 // make_rect_valid 32 static inline void 33 make_rect_valid(BRect& rect) 34 { 35 if (rect.left > rect.right) { 36 float temp = rect.left; 37 rect.left = rect.right; 38 rect.right = temp; 39 } 40 if (rect.top > rect.bottom) { 41 float temp = rect.top; 42 rect.top = rect.bottom; 43 rect.bottom = temp; 44 } 45 } 46 47 // extend_by_stroke_width 48 static inline void 49 extend_by_stroke_width(BRect& rect, float penSize) 50 { 51 // "- 0.5" because if stroke width == 1, we don't need to extend 52 float inset = -ceilf(penSize / 2.0 - 0.5); 53 rect.InsetBy(inset, inset); 54 } 55 56 57 class AutoFloatingOverlaysHider { 58 public: 59 AutoFloatingOverlaysHider(HWInterface* interface, const BRect& area) 60 : fInterface(interface) 61 , fHidden(interface->HideFloatingOverlays(area)) 62 { 63 } 64 65 AutoFloatingOverlaysHider(HWInterface* interface) 66 : fInterface(interface) 67 , fHidden(fInterface->HideFloatingOverlays()) 68 { 69 } 70 71 ~AutoFloatingOverlaysHider() 72 { 73 if (fHidden) 74 fInterface->ShowFloatingOverlays(); 75 } 76 77 bool WasHidden() const 78 { 79 return fHidden; 80 } 81 82 private: 83 HWInterface* fInterface; 84 bool fHidden; 85 86 }; 87 88 89 // #pragma mark - 90 91 92 DrawingEngine::DrawingEngine(HWInterface* interface) 93 : fPainter(new Painter()), 94 fGraphicsCard(NULL), 95 fAvailableHWAccleration(0), 96 fSuspendSyncLevel(0), 97 fCopyToFront(true) 98 { 99 SetHWInterface(interface); 100 } 101 102 103 DrawingEngine::~DrawingEngine() 104 { 105 SetHWInterface(NULL); 106 delete fPainter; 107 } 108 109 110 // #pragma mark - locking 111 112 113 bool 114 DrawingEngine::LockParallelAccess() 115 { 116 return fGraphicsCard->LockParallelAccess(); 117 } 118 119 120 void 121 DrawingEngine::UnlockParallelAccess() 122 { 123 fGraphicsCard->UnlockParallelAccess(); 124 } 125 126 127 bool 128 DrawingEngine::LockExclusiveAccess() 129 { 130 return fGraphicsCard->LockExclusiveAccess(); 131 } 132 133 134 bool 135 DrawingEngine::IsExclusiveAccessLocked() 136 { 137 return fGraphicsCard->IsExclusiveAccessLocked(); 138 } 139 140 141 void 142 DrawingEngine::UnlockExclusiveAccess() 143 { 144 fGraphicsCard->UnlockExclusiveAccess(); 145 } 146 147 // #pragma mark - 148 149 void 150 DrawingEngine::FrameBufferChanged() 151 { 152 if (!fGraphicsCard) { 153 fPainter->DetachFromBuffer(); 154 fAvailableHWAccleration = 0; 155 return; 156 } 157 158 // NOTE: locking is probably bogus, since we are called 159 // in the thread that changed the frame buffer... 160 if (LockExclusiveAccess()) { 161 fPainter->AttachToBuffer(fGraphicsCard->DrawingBuffer()); 162 // available HW acceleration might have changed 163 fAvailableHWAccleration = fGraphicsCard->AvailableHWAcceleration(); 164 UnlockExclusiveAccess(); 165 } 166 } 167 168 169 void 170 DrawingEngine::SetHWInterface(HWInterface* interface) 171 { 172 if (fGraphicsCard == interface) 173 return; 174 175 if (fGraphicsCard) 176 fGraphicsCard->RemoveListener(this); 177 178 fGraphicsCard = interface; 179 180 if (fGraphicsCard) 181 fGraphicsCard->AddListener(this); 182 183 FrameBufferChanged(); 184 } 185 186 187 void 188 DrawingEngine::SetCopyToFrontEnabled(bool enable) 189 { 190 fCopyToFront = enable; 191 } 192 193 194 void 195 DrawingEngine::CopyToFront(/*const*/ BRegion& region) 196 { 197 fGraphicsCard->InvalidateRegion(region); 198 } 199 200 201 // #pragma mark - 202 203 //! the DrawingEngine needs to be locked! 204 void 205 DrawingEngine::ConstrainClippingRegion(const BRegion* region) 206 { 207 CRASH_IF_NOT_LOCKED 208 209 fPainter->ConstrainClipping(region); 210 } 211 212 213 void 214 DrawingEngine::SetDrawState(const DrawState* state, int32 xOffset, int32 yOffset) 215 { 216 fPainter->SetDrawState(state, xOffset, yOffset); 217 } 218 219 220 void 221 DrawingEngine::SetHighColor(const rgb_color& color) 222 { 223 fPainter->SetHighColor(color); 224 } 225 226 227 void 228 DrawingEngine::SetLowColor(const rgb_color& color) 229 { 230 fPainter->SetLowColor(color); 231 } 232 233 234 void 235 DrawingEngine::SetPenSize(float size) 236 { 237 fPainter->SetPenSize(size); 238 } 239 240 241 void 242 DrawingEngine::SetStrokeMode(cap_mode lineCap, join_mode joinMode, 243 float miterLimit) 244 { 245 fPainter->SetStrokeMode(lineCap, joinMode, miterLimit); 246 } 247 248 249 void 250 DrawingEngine::SetBlendingMode(source_alpha srcAlpha, alpha_function alphaFunc) 251 { 252 fPainter->SetBlendingMode(srcAlpha, alphaFunc); 253 } 254 255 256 void 257 DrawingEngine::SetPattern(const struct pattern& pattern) 258 { 259 fPainter->SetPattern(pattern, false); 260 } 261 262 263 void 264 DrawingEngine::SetDrawingMode(drawing_mode mode) 265 { 266 fPainter->SetDrawingMode(mode); 267 } 268 269 270 void 271 DrawingEngine::SetDrawingMode(drawing_mode mode, drawing_mode& oldMode) 272 { 273 oldMode = fPainter->DrawingMode(); 274 fPainter->SetDrawingMode(mode); 275 } 276 277 278 void 279 DrawingEngine::SetFont(const ServerFont& font) 280 { 281 fPainter->SetFont(font); 282 } 283 284 285 void 286 DrawingEngine::SetFont(const DrawState* state) 287 { 288 fPainter->SetFont(state); 289 } 290 291 292 // #pragma mark - 293 294 295 void 296 DrawingEngine::SuspendAutoSync() 297 { 298 CRASH_IF_NOT_LOCKED 299 300 fSuspendSyncLevel++; 301 } 302 303 304 void 305 DrawingEngine::Sync() 306 { 307 CRASH_IF_NOT_LOCKED 308 309 fSuspendSyncLevel--; 310 if (fSuspendSyncLevel == 0) 311 fGraphicsCard->Sync(); 312 } 313 314 // #pragma mark - 315 316 // CopyRegion() does a topological sort of the rects in the 317 // region. The algorithm was suggested by Ingo Weinhold. 318 // It compares each rect with each rect and builds a tree 319 // of successors so we know the order in which they can be copied. 320 // For example, let's suppose these rects are in a BRegion: 321 // ************ 322 // * B * 323 // ************ 324 // ************* 325 // * * 326 // * A **************** 327 // * ** * 328 // ************** * 329 // * C * 330 // * * 331 // * * 332 // *************** 333 // When copying stuff from LEFT TO RIGHT, TOP TO BOTTOM, the 334 // result of the sort will be C, A, B. For this direction, we search 335 // for the rects that have no neighbors to their right and to their 336 // bottom, These can be copied without drawing into the area of 337 // rects yet to be copied. If you move from RIGHT TO LEFT, BOTTOM TO TOP, 338 // you go look for the ones that have no neighbors to their top and left. 339 // 340 // Here I draw some rays to illustrate LEFT TO RIGHT, TOP TO BOTTOM: 341 // ************ 342 // * B * 343 // ************ 344 // ************* 345 // * * 346 // * A ****************----------------- 347 // * ** * 348 // ************** * 349 // * C * 350 // * * 351 // * * 352 // *************** 353 // | 354 // | 355 // | 356 // | 357 // There are no rects in the area defined by the rays to the right 358 // and bottom of rect C, so that's the one we want to copy first 359 // (for positive x and y offsets). 360 // Since A is to the left of C and B is to the top of C, The "node" 361 // for C will point to the nodes of A and B as its "successors". Therefor, 362 // A and B will have an "indegree" of 1 for C pointing to them. C will 363 // have an "indegree" of 0, because there was no rect to which C 364 // was to the left or top of. When comparing A and B, neither is left 365 // or top from the other and in the sense that the algorithm cares about. 366 367 // NOTE: comparison of coordinates assumes that rects don't overlap 368 // and don't share the actual edge either (as is the case in BRegions). 369 370 struct node { 371 node() 372 { 373 pointers = NULL; 374 } 375 node(const BRect& r, int32 maxPointers) 376 { 377 init(r, maxPointers); 378 } 379 ~node() 380 { 381 delete [] pointers; 382 } 383 384 void init(const BRect& r, int32 maxPointers) 385 { 386 rect = r; 387 pointers = new node*[maxPointers]; 388 in_degree = 0; 389 next_pointer = 0; 390 } 391 392 void push(node* node) 393 { 394 pointers[next_pointer] = node; 395 next_pointer++; 396 } 397 node* top() 398 { 399 return pointers[next_pointer]; 400 } 401 node* pop() 402 { 403 node* ret = top(); 404 next_pointer--; 405 return ret; 406 } 407 408 BRect rect; 409 int32 in_degree; 410 node** pointers; 411 int32 next_pointer; 412 }; 413 414 static bool 415 is_left_of(const BRect& a, const BRect& b) 416 { 417 return (a.right < b.left); 418 } 419 static bool 420 is_above(const BRect& a, const BRect& b) 421 { 422 return (a.bottom < b.top); 423 } 424 425 // CopyRegion 426 void 427 DrawingEngine::CopyRegion(/*const*/ BRegion* region, 428 int32 xOffset, int32 yOffset) 429 { 430 CRASH_IF_NOT_EXCLUSIVE_LOCKED 431 432 BRect frame = region->Frame(); 433 frame = frame | frame.OffsetByCopy(xOffset, yOffset); 434 435 AutoFloatingOverlaysHider _(fGraphicsCard, frame); 436 437 int32 count = region->CountRects(); 438 439 // TODO: make this step unnecessary 440 // (by using different stack impl inside node) 441 node nodes[count]; 442 for (int32 i= 0; i < count; i++) { 443 nodes[i].init(region->RectAt(i), count); 444 } 445 446 for (int32 i = 0; i < count; i++) { 447 BRect a = region->RectAt(i); 448 for (int32 k = i + 1; k < count; k++) { 449 BRect b = region->RectAt(k); 450 int cmp = 0; 451 // compare horizontally 452 if (xOffset > 0) { 453 if (is_left_of(a, b)) { 454 cmp -= 1; 455 } else if (is_left_of(b, a)) { 456 cmp += 1; 457 } 458 } else if (xOffset < 0) { 459 if (is_left_of(a, b)) { 460 cmp += 1; 461 } else if (is_left_of(b, a)) { 462 cmp -= 1; 463 } 464 } 465 // compare vertically 466 if (yOffset > 0) { 467 if (is_above(a, b)) { 468 cmp -= 1; 469 } else if (is_above(b, a)) { 470 cmp += 1; 471 } 472 } else if (yOffset < 0) { 473 if (is_above(a, b)) { 474 cmp += 1; 475 } else if (is_above(b, a)) { 476 cmp -= 1; 477 } 478 } 479 // add appropriate node as successor 480 if (cmp > 0) { 481 nodes[i].push(&nodes[k]); 482 nodes[k].in_degree++; 483 } else if (cmp < 0) { 484 nodes[k].push(&nodes[i]); 485 nodes[i].in_degree++; 486 } 487 } 488 } 489 // put all nodes onto a stack that have an "indegree" count of zero 490 std::stack<node*> inDegreeZeroNodes; 491 for (int32 i = 0; i < count; i++) { 492 if (nodes[i].in_degree == 0) { 493 inDegreeZeroNodes.push(&nodes[i]); 494 } 495 } 496 // pop the rects from the stack, do the actual copy operation 497 // and decrease the "indegree" count of the other rects not 498 // currently on the stack and to which the current rect pointed 499 // to. If their "indegree" count reaches zero, put them onto the 500 // stack as well. 501 502 clipping_rect* sortedRectList = NULL; 503 int32 nextSortedIndex = 0; 504 505 if (fAvailableHWAccleration & HW_ACC_COPY_REGION) 506 sortedRectList = new clipping_rect[count]; 507 508 while (!inDegreeZeroNodes.empty()) { 509 node* n = inDegreeZeroNodes.top(); 510 inDegreeZeroNodes.pop(); 511 512 // do the software implementation or add to sorted 513 // rect list for using the HW accelerated version 514 // later 515 if (sortedRectList) { 516 sortedRectList[nextSortedIndex].left = (int32)n->rect.left; 517 sortedRectList[nextSortedIndex].top = (int32)n->rect.top; 518 sortedRectList[nextSortedIndex].right = (int32)n->rect.right; 519 sortedRectList[nextSortedIndex].bottom = (int32)n->rect.bottom; 520 nextSortedIndex++; 521 } else { 522 BRect touched = CopyRect(n->rect, xOffset, yOffset); 523 fGraphicsCard->Invalidate(touched); 524 } 525 526 for (int32 k = 0; k < n->next_pointer; k++) { 527 n->pointers[k]->in_degree--; 528 if (n->pointers[k]->in_degree == 0) 529 inDegreeZeroNodes.push(n->pointers[k]); 530 } 531 } 532 533 // trigger the HW accelerated version if it was available 534 if (sortedRectList) { 535 fGraphicsCard->CopyRegion(sortedRectList, count, xOffset, yOffset); 536 if (fGraphicsCard->IsDoubleBuffered()) { 537 fGraphicsCard->Invalidate( 538 region->Frame().OffsetByCopy(xOffset, yOffset)); 539 } 540 } 541 542 delete[] sortedRectList; 543 } 544 545 // InvertRect 546 void 547 DrawingEngine::InvertRect(BRect r) 548 { 549 CRASH_IF_NOT_LOCKED 550 551 make_rect_valid(r); 552 r = fPainter->ClipRect(r); 553 if (!r.IsValid()) 554 return; 555 556 AutoFloatingOverlaysHider _(fGraphicsCard, r); 557 558 // try hardware optimized version first 559 if (fAvailableHWAccleration & HW_ACC_INVERT_REGION) { 560 BRegion region(r); 561 region.IntersectWith(fPainter->ClippingRegion()); 562 fGraphicsCard->InvertRegion(region); 563 } else { 564 fPainter->InvertRect(r); 565 } 566 567 _CopyToFront(r); 568 } 569 570 // DrawBitmap 571 void 572 DrawingEngine::DrawBitmap(ServerBitmap* bitmap, const BRect& bitmapRect, 573 const BRect& viewRect, uint32 options) 574 { 575 CRASH_IF_NOT_LOCKED 576 577 BRect clipped = fPainter->ClipRect(viewRect); 578 if (clipped.IsValid()) { 579 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 580 581 fPainter->DrawBitmap(bitmap, bitmapRect, viewRect, options); 582 583 _CopyToFront(clipped); 584 } 585 } 586 587 // DrawArc 588 void 589 DrawingEngine::DrawArc(BRect r, const float& angle, const float& span, 590 bool filled) 591 { 592 CRASH_IF_NOT_LOCKED 593 594 make_rect_valid(r); 595 fPainter->AlignEllipseRect(&r, filled); 596 BRect clipped(r); 597 598 if (!filled) 599 extend_by_stroke_width(clipped, fPainter->PenSize()); 600 601 clipped = fPainter->ClipRect(r); 602 603 if (clipped.IsValid()) { 604 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 605 606 float xRadius = r.Width() / 2.0; 607 float yRadius = r.Height() / 2.0; 608 BPoint center(r.left + xRadius, 609 r.top + yRadius); 610 611 if (filled) 612 fPainter->FillArc(center, xRadius, yRadius, angle, span); 613 else 614 fPainter->StrokeArc(center, xRadius, yRadius, angle, span); 615 616 _CopyToFront(clipped); 617 } 618 } 619 620 void 621 DrawingEngine::FillArc(BRect r, const float& angle, const float& span, 622 const BGradient& gradient) 623 { 624 CRASH_IF_NOT_LOCKED 625 626 make_rect_valid(r); 627 fPainter->AlignEllipseRect(&r, true); 628 BRect clipped(r); 629 630 clipped = fPainter->ClipRect(r); 631 632 if (clipped.IsValid()) { 633 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 634 635 float xRadius = r.Width() / 2.0; 636 float yRadius = r.Height() / 2.0; 637 BPoint center(r.left + xRadius, 638 r.top + yRadius); 639 640 fPainter->FillArc(center, xRadius, yRadius, angle, span, gradient); 641 642 _CopyToFront(clipped); 643 } 644 } 645 646 647 void 648 DrawingEngine::DrawBezier(BPoint* pts, bool filled) 649 { 650 CRASH_IF_NOT_LOCKED 651 652 // TODO: figure out bounds and hide cursor depending on that 653 AutoFloatingOverlaysHider _(fGraphicsCard); 654 655 BRect touched = fPainter->DrawBezier(pts, filled); 656 657 _CopyToFront(touched); 658 } 659 660 661 void 662 DrawingEngine::FillBezier(BPoint* pts, const BGradient& gradient) 663 { 664 CRASH_IF_NOT_LOCKED 665 666 // TODO: figure out bounds and hide cursor depending on that 667 AutoFloatingOverlaysHider _(fGraphicsCard); 668 669 BRect touched = fPainter->FillBezier(pts, gradient); 670 671 _CopyToFront(touched); 672 } 673 674 675 void 676 DrawingEngine::DrawEllipse(BRect r, bool filled) 677 { 678 CRASH_IF_NOT_LOCKED 679 680 make_rect_valid(r); 681 BRect clipped = r; 682 fPainter->AlignEllipseRect(&clipped, filled); 683 684 if (!filled) 685 extend_by_stroke_width(clipped, fPainter->PenSize()); 686 687 clipped.left = floorf(clipped.left); 688 clipped.top = floorf(clipped.top); 689 clipped.right = ceilf(clipped.right); 690 clipped.bottom = ceilf(clipped.bottom); 691 692 clipped = fPainter->ClipRect(clipped); 693 694 if (clipped.IsValid()) { 695 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 696 697 fPainter->DrawEllipse(r, filled); 698 699 _CopyToFront(clipped); 700 } 701 } 702 703 704 void 705 DrawingEngine::FillEllipse(BRect r, const BGradient& gradient) 706 { 707 CRASH_IF_NOT_LOCKED 708 709 make_rect_valid(r); 710 BRect clipped = r; 711 fPainter->AlignEllipseRect(&clipped, true); 712 713 clipped.left = floorf(clipped.left); 714 clipped.top = floorf(clipped.top); 715 clipped.right = ceilf(clipped.right); 716 clipped.bottom = ceilf(clipped.bottom); 717 718 clipped = fPainter->ClipRect(clipped); 719 720 if (clipped.IsValid()) { 721 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 722 723 fPainter->FillEllipse(r, gradient); 724 725 _CopyToFront(clipped); 726 } 727 } 728 729 730 void 731 DrawingEngine::DrawPolygon(BPoint* ptlist, int32 numpts, BRect bounds, 732 bool filled, bool closed) 733 { 734 CRASH_IF_NOT_LOCKED 735 736 make_rect_valid(bounds); 737 if (!filled) 738 extend_by_stroke_width(bounds, fPainter->PenSize()); 739 bounds = fPainter->ClipRect(bounds); 740 if (bounds.IsValid()) { 741 AutoFloatingOverlaysHider _(fGraphicsCard, bounds); 742 743 fPainter->DrawPolygon(ptlist, numpts, filled, closed); 744 745 _CopyToFront(bounds); 746 } 747 } 748 749 750 void 751 DrawingEngine::FillPolygon(BPoint* ptlist, int32 numpts, BRect bounds, 752 const BGradient& gradient, bool closed) 753 { 754 CRASH_IF_NOT_LOCKED 755 756 make_rect_valid(bounds); 757 bounds = fPainter->ClipRect(bounds); 758 if (bounds.IsValid()) { 759 AutoFloatingOverlaysHider _(fGraphicsCard, bounds); 760 761 fPainter->FillPolygon(ptlist, numpts, gradient, closed); 762 763 _CopyToFront(bounds); 764 } 765 } 766 767 768 // #pragma mark - rgb_color 769 770 771 void 772 DrawingEngine::StrokePoint(const BPoint& pt, const rgb_color& color) 773 { 774 StrokeLine(pt, pt, color); 775 } 776 777 // StrokeLine 778 // 779 // * this function is only used by Decorators 780 // * it assumes a one pixel wide line 781 void 782 DrawingEngine::StrokeLine(const BPoint& start, const BPoint& end, 783 const rgb_color& color) 784 { 785 CRASH_IF_NOT_LOCKED 786 787 BRect touched(start, end); 788 make_rect_valid(touched); 789 touched = fPainter->ClipRect(touched); 790 AutoFloatingOverlaysHider _(fGraphicsCard, touched); 791 792 if (!fPainter->StraightLine(start, end, color)) { 793 rgb_color previousColor = fPainter->HighColor(); 794 drawing_mode previousMode = fPainter->DrawingMode(); 795 796 fPainter->SetHighColor(color); 797 fPainter->SetDrawingMode(B_OP_OVER); 798 fPainter->StrokeLine(start, end); 799 800 fPainter->SetDrawingMode(previousMode); 801 fPainter->SetHighColor(previousColor); 802 } 803 804 _CopyToFront(touched); 805 } 806 807 // this function is used to draw a one pixel wide rect 808 void 809 DrawingEngine::StrokeRect(BRect r, const rgb_color &color) 810 { 811 CRASH_IF_NOT_LOCKED 812 813 make_rect_valid(r); 814 BRect clipped = fPainter->ClipRect(r); 815 if (clipped.IsValid()) { 816 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 817 818 fPainter->StrokeRect(r, color); 819 820 _CopyToFront(clipped); 821 } 822 } 823 824 825 void 826 DrawingEngine::FillRect(BRect r, const rgb_color& color) 827 { 828 CRASH_IF_NOT_LOCKED 829 830 // NOTE: Write locking because we might use HW acceleration. 831 // This needs to be investigated, I'm doing this because of 832 // gut feeling. 833 make_rect_valid(r); 834 r = fPainter->ClipRect(r); 835 if (!r.IsValid()) 836 return; 837 838 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, r); 839 840 // try hardware optimized version first 841 if (fAvailableHWAccleration & HW_ACC_FILL_REGION) { 842 BRegion region(r); 843 region.IntersectWith(fPainter->ClippingRegion()); 844 fGraphicsCard->FillRegion(region, color, 845 fSuspendSyncLevel == 0 || overlaysHider.WasHidden()); 846 } else { 847 fPainter->FillRect(r, color); 848 } 849 850 _CopyToFront(r); 851 } 852 853 854 void 855 DrawingEngine::FillRegion(BRegion& r, const rgb_color& color) 856 { 857 CRASH_IF_NOT_LOCKED 858 859 // NOTE: region expected to be already clipped correctly!! 860 BRect frame = r.Frame(); 861 if (!fPainter->Bounds().Contains(frame)) { 862 // NOTE: I am not quite sure yet how this can happen, but apparently it 863 // can (see bug #634). 864 // This function is used for internal app_server painting, in the case of 865 // bug #634, the background of views is painted. But the view region 866 // should never be outside the frame buffer bounds. 867 // char message[1024]; 868 // BRect bounds = fPainter->Bounds(); 869 // sprintf(message, "FillRegion() - painter: (%d, %d)->(%d, %d), region: (%d, %d)->(%d, %d)", 870 // (int)bounds.left, (int)bounds.top, (int)bounds.right, (int)bounds.bottom, 871 // (int)frame.left, (int)frame.top, (int)frame.right, (int)frame.bottom); 872 // debugger(message); 873 return; 874 } 875 876 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, frame); 877 878 // try hardware optimized version first 879 if ((fAvailableHWAccleration & HW_ACC_FILL_REGION) != 0 880 && frame.Width() * frame.Height() > 100) { 881 fGraphicsCard->FillRegion(r, color, fSuspendSyncLevel == 0 882 || overlaysHider.WasHidden()); 883 } else { 884 int32 count = r.CountRects(); 885 for (int32 i = 0; i < count; i++) 886 fPainter->FillRectNoClipping(r.RectAtInt(i), color); 887 } 888 889 _CopyToFront(frame); 890 } 891 892 // #pragma mark - DrawState 893 894 void 895 DrawingEngine::StrokeRect(BRect r) 896 { 897 CRASH_IF_NOT_LOCKED 898 899 // support invalid rects 900 make_rect_valid(r); 901 BRect clipped(r); 902 extend_by_stroke_width(clipped, fPainter->PenSize()); 903 clipped = fPainter->ClipRect(clipped); 904 if (clipped.IsValid()) { 905 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 906 907 fPainter->StrokeRect(r); 908 909 _CopyToFront(clipped); 910 } 911 } 912 913 914 void 915 DrawingEngine::FillRect(BRect r) 916 { 917 CRASH_IF_NOT_LOCKED 918 919 make_rect_valid(r); 920 r = fPainter->AlignAndClipRect(r); 921 if (!r.IsValid()) 922 return; 923 924 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, r); 925 926 bool doInSoftware = true; 927 if ((r.Width() + 1) * (r.Height() + 1) > 100.0) { 928 // try hardware optimized version first 929 // if the rect is large enough 930 if ((fAvailableHWAccleration & HW_ACC_FILL_REGION) != 0) { 931 if (fPainter->Pattern() == B_SOLID_HIGH 932 && (fPainter->DrawingMode() == B_OP_COPY 933 || fPainter->DrawingMode() == B_OP_OVER)) { 934 BRegion region(r); 935 region.IntersectWith(fPainter->ClippingRegion()); 936 fGraphicsCard->FillRegion(region, fPainter->HighColor(), 937 fSuspendSyncLevel == 0 938 || overlaysHider.WasHidden()); 939 doInSoftware = false; 940 } else if (fPainter->Pattern() == B_SOLID_LOW 941 && fPainter->DrawingMode() == B_OP_COPY) { 942 BRegion region(r); 943 region.IntersectWith(fPainter->ClippingRegion()); 944 fGraphicsCard->FillRegion(region, fPainter->LowColor(), 945 fSuspendSyncLevel == 0 946 || overlaysHider.WasHidden()); 947 doInSoftware = false; 948 } 949 } 950 } 951 952 if (doInSoftware && fAvailableHWAccleration & HW_ACC_INVERT_REGION 953 && fPainter->Pattern() == B_SOLID_HIGH 954 && fPainter->DrawingMode() == B_OP_INVERT) { 955 BRegion region(r); 956 region.IntersectWith(fPainter->ClippingRegion()); 957 fGraphicsCard->InvertRegion(region); 958 doInSoftware = false; 959 } 960 961 if (doInSoftware) 962 fPainter->FillRect(r); 963 964 _CopyToFront(r); 965 } 966 967 968 void 969 DrawingEngine::FillRect(BRect r, const BGradient& gradient) 970 { 971 CRASH_IF_NOT_LOCKED 972 973 make_rect_valid(r); 974 r = fPainter->AlignAndClipRect(r); 975 if (!r.IsValid()) 976 return; 977 978 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, r); 979 980 fPainter->FillRect(r, gradient); 981 982 _CopyToFront(r); 983 } 984 985 986 void 987 DrawingEngine::FillRegion(BRegion& r) 988 { 989 CRASH_IF_NOT_LOCKED 990 991 BRect clipped = fPainter->ClipRect(r.Frame()); 992 if (!clipped.IsValid()) 993 return; 994 995 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, clipped); 996 997 bool doInSoftware = true; 998 // try hardware optimized version first 999 if ((fAvailableHWAccleration & HW_ACC_FILL_REGION) != 0) { 1000 if (fPainter->Pattern() == B_SOLID_HIGH 1001 && (fPainter->DrawingMode() == B_OP_COPY 1002 || fPainter->DrawingMode() == B_OP_OVER)) { 1003 r.IntersectWith(fPainter->ClippingRegion()); 1004 fGraphicsCard->FillRegion(r, fPainter->HighColor(), 1005 fSuspendSyncLevel == 0 1006 || overlaysHider.WasHidden()); 1007 doInSoftware = false; 1008 } else if (fPainter->Pattern() == B_SOLID_LOW 1009 && fPainter->DrawingMode() == B_OP_COPY) { 1010 r.IntersectWith(fPainter->ClippingRegion()); 1011 fGraphicsCard->FillRegion(r, fPainter->LowColor(), 1012 fSuspendSyncLevel == 0 1013 || overlaysHider.WasHidden()); 1014 doInSoftware = false; 1015 } 1016 } 1017 1018 if (doInSoftware && fAvailableHWAccleration & HW_ACC_INVERT_REGION 1019 && fPainter->Pattern() == B_SOLID_HIGH 1020 && fPainter->DrawingMode() == B_OP_INVERT) { 1021 r.IntersectWith(fPainter->ClippingRegion()); 1022 fGraphicsCard->InvertRegion(r); 1023 doInSoftware = false; 1024 } 1025 1026 if (doInSoftware) { 1027 1028 BRect touched = fPainter->FillRect(r.RectAt(0)); 1029 1030 int32 count = r.CountRects(); 1031 for (int32 i = 1; i < count; i++) 1032 touched = touched | fPainter->FillRect(r.RectAt(i)); 1033 } 1034 1035 _CopyToFront(r.Frame()); 1036 } 1037 1038 1039 void 1040 DrawingEngine::FillRegion(BRegion& r, const BGradient& gradient) 1041 { 1042 CRASH_IF_NOT_LOCKED 1043 1044 BRect clipped = fPainter->ClipRect(r.Frame()); 1045 if (!clipped.IsValid()) 1046 return; 1047 1048 AutoFloatingOverlaysHider overlaysHider(fGraphicsCard, clipped); 1049 1050 BRect touched = fPainter->FillRect(r.RectAt(0), gradient); 1051 1052 int32 count = r.CountRects(); 1053 for (int32 i = 1; i < count; i++) 1054 touched = touched | fPainter->FillRect(r.RectAt(i), gradient); 1055 1056 _CopyToFront(r.Frame()); 1057 } 1058 1059 1060 void 1061 DrawingEngine::DrawRoundRect(BRect r, float xrad, float yrad, bool filled) 1062 { 1063 CRASH_IF_NOT_LOCKED 1064 1065 // NOTE: the stroke does not extend past "r" in R5, 1066 // though I consider this unexpected behaviour. 1067 make_rect_valid(r); 1068 BRect clipped = fPainter->ClipRect(r); 1069 1070 clipped.left = floorf(clipped.left); 1071 clipped.top = floorf(clipped.top); 1072 clipped.right = ceilf(clipped.right); 1073 clipped.bottom = ceilf(clipped.bottom); 1074 1075 if (clipped.IsValid()) { 1076 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 1077 1078 BRect touched = filled ? fPainter->FillRoundRect(r, xrad, yrad) 1079 : fPainter->StrokeRoundRect(r, xrad, yrad); 1080 1081 _CopyToFront(touched); 1082 } 1083 } 1084 1085 1086 void 1087 DrawingEngine::FillRoundRect(BRect r, float xrad, float yrad, 1088 const BGradient& gradient) 1089 { 1090 CRASH_IF_NOT_LOCKED 1091 1092 // NOTE: the stroke does not extend past "r" in R5, 1093 // though I consider this unexpected behaviour. 1094 make_rect_valid(r); 1095 BRect clipped = fPainter->ClipRect(r); 1096 1097 clipped.left = floorf(clipped.left); 1098 clipped.top = floorf(clipped.top); 1099 clipped.right = ceilf(clipped.right); 1100 clipped.bottom = ceilf(clipped.bottom); 1101 1102 if (clipped.IsValid()) { 1103 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 1104 1105 BRect touched = fPainter->FillRoundRect(r, xrad, yrad, gradient); 1106 1107 _CopyToFront(touched); 1108 } 1109 } 1110 1111 1112 void 1113 DrawingEngine::DrawShape(const BRect& bounds, int32 opCount, 1114 const uint32* opList, int32 ptCount, const BPoint* ptList, bool filled) 1115 { 1116 CRASH_IF_NOT_LOCKED 1117 1118 // NOTE: hides cursor regardless of if and where 1119 // shape is drawn on screen, TODO: optimize 1120 AutoFloatingOverlaysHider _(fGraphicsCard); 1121 1122 BRect touched = fPainter->DrawShape(opCount, opList, 1123 ptCount, ptList, 1124 filled); 1125 1126 _CopyToFront(touched); 1127 } 1128 1129 1130 void 1131 DrawingEngine::FillShape(const BRect& bounds, int32 opCount, 1132 const uint32* opList, int32 ptCount, const BPoint* ptList, 1133 const BGradient& gradient) 1134 { 1135 CRASH_IF_NOT_LOCKED 1136 1137 // NOTE: hides cursor regardless of if and where 1138 // shape is drawn on screen, TODO: optimize 1139 AutoFloatingOverlaysHider _(fGraphicsCard); 1140 1141 BRect touched = fPainter->FillShape(opCount, opList, ptCount, 1142 ptList, gradient); 1143 1144 _CopyToFront(touched); 1145 } 1146 1147 1148 void 1149 DrawingEngine::DrawTriangle(BPoint* pts, const BRect& bounds, bool filled) 1150 { 1151 CRASH_IF_NOT_LOCKED 1152 1153 BRect clipped(bounds); 1154 if (!filled) 1155 extend_by_stroke_width(clipped, fPainter->PenSize()); 1156 clipped = fPainter->ClipRect(clipped); 1157 if (clipped.IsValid()) { 1158 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 1159 1160 if (filled) 1161 fPainter->FillTriangle(pts[0], pts[1], pts[2]); 1162 else 1163 fPainter->StrokeTriangle(pts[0], pts[1], pts[2]); 1164 1165 _CopyToFront(clipped); 1166 } 1167 } 1168 1169 void 1170 DrawingEngine::FillTriangle(BPoint* pts, const BRect& bounds, 1171 const BGradient& gradient) 1172 { 1173 CRASH_IF_NOT_LOCKED 1174 1175 BRect clipped(bounds); 1176 clipped = fPainter->ClipRect(clipped); 1177 if (clipped.IsValid()) { 1178 AutoFloatingOverlaysHider _(fGraphicsCard, clipped); 1179 1180 fPainter->FillTriangle(pts[0], pts[1], pts[2], gradient); 1181 1182 _CopyToFront(clipped); 1183 } 1184 } 1185 1186 // StrokeLine 1187 void 1188 DrawingEngine::StrokeLine(const BPoint &start, const BPoint &end) 1189 { 1190 CRASH_IF_NOT_LOCKED 1191 1192 BRect touched(start, end); 1193 make_rect_valid(touched); 1194 extend_by_stroke_width(touched, fPainter->PenSize()); 1195 touched = fPainter->ClipRect(touched); 1196 if (touched.IsValid()) { 1197 AutoFloatingOverlaysHider _(fGraphicsCard, touched); 1198 1199 fPainter->StrokeLine(start, end); 1200 1201 _CopyToFront(touched); 1202 } 1203 } 1204 1205 // StrokeLineArray 1206 void 1207 DrawingEngine::StrokeLineArray(int32 numLines, 1208 const ViewLineArrayInfo *lineData) 1209 { 1210 CRASH_IF_NOT_LOCKED 1211 1212 if (!lineData || numLines <= 0) 1213 return; 1214 1215 // figure out bounding box for line array 1216 const ViewLineArrayInfo* data = (const ViewLineArrayInfo*)&(lineData[0]); 1217 BRect touched(min_c(data->startPoint.x, data->endPoint.x), 1218 min_c(data->startPoint.y, data->endPoint.y), 1219 max_c(data->startPoint.x, data->endPoint.x), 1220 max_c(data->startPoint.y, data->endPoint.y)); 1221 1222 for (int32 i = 1; i < numLines; i++) { 1223 data = (const ViewLineArrayInfo*)&(lineData[i]); 1224 BRect box(min_c(data->startPoint.x, data->endPoint.x), 1225 min_c(data->startPoint.y, data->endPoint.y), 1226 max_c(data->startPoint.x, data->endPoint.x), 1227 max_c(data->startPoint.y, data->endPoint.y)); 1228 touched = touched | box; 1229 } 1230 extend_by_stroke_width(touched, fPainter->PenSize()); 1231 touched = fPainter->ClipRect(touched); 1232 if (touched.IsValid()) { 1233 AutoFloatingOverlaysHider _(fGraphicsCard, touched); 1234 1235 data = (const ViewLineArrayInfo*)&(lineData[0]); 1236 1237 // store current graphics state, we mess with the 1238 // high color and pattern... 1239 rgb_color oldColor = fPainter->HighColor(); 1240 struct pattern pattern = fPainter->Pattern(); 1241 1242 fPainter->SetHighColor(data->color); 1243 fPainter->SetPattern(B_SOLID_HIGH); 1244 fPainter->StrokeLine(data->startPoint, data->endPoint); 1245 1246 for (int32 i = 1; i < numLines; i++) { 1247 data = (const ViewLineArrayInfo*)&(lineData[i]); 1248 fPainter->SetHighColor(data->color); 1249 fPainter->StrokeLine(data->startPoint, data->endPoint); 1250 } 1251 1252 // restore correct drawing state highcolor and pattern 1253 fPainter->SetHighColor(oldColor); 1254 fPainter->SetPattern(pattern); 1255 1256 _CopyToFront(touched); 1257 } 1258 } 1259 1260 // #pragma mark - 1261 1262 BPoint 1263 DrawingEngine::DrawString(const char* string, int32 length, 1264 const BPoint& pt, escapement_delta* delta) 1265 { 1266 CRASH_IF_NOT_LOCKED 1267 1268 BPoint penLocation = pt; 1269 1270 // try a fast clipping path 1271 if (fPainter->ClippingRegion() && fPainter->Font().Rotation() == 0.0f) { 1272 float fontSize = fPainter->Font().Size(); 1273 BRect clippingFrame = fPainter->ClippingRegion()->Frame(); 1274 if (pt.x - fontSize > clippingFrame.right 1275 || pt.y + fontSize < clippingFrame.top 1276 || pt.y - fontSize > clippingFrame.bottom) { 1277 penLocation.x += StringWidth(string, length, delta); 1278 return penLocation; 1279 } 1280 } 1281 1282 // use a FontCacheRefernece to speed up the second pass of 1283 // drawing the string 1284 FontCacheReference cacheReference; 1285 1286 //bigtime_t now = system_time(); 1287 // TODO: BoundingBox is quite slow!! Optimizing it will be beneficial. 1288 // Cursiously, the DrawString after it is actually faster!?! 1289 // TODO: make the availability of the hardware cursor part of the 1290 // HW acceleration flags and skip all calculations for HideFloatingOverlays 1291 // in case we don't have one. 1292 // TODO: Watch out about penLocation and use Painter::PenLocation() when 1293 // not using BoundindBox anymore. 1294 BRect b = fPainter->BoundingBox(string, length, pt, &penLocation, delta, 1295 &cacheReference); 1296 // stop here if we're supposed to render outside of the clipping 1297 b = fPainter->ClipRect(b); 1298 if (b.IsValid()) { 1299 //printf("bounding box '%s': %lld µs\n", string, system_time() - now); 1300 AutoFloatingOverlaysHider _(fGraphicsCard, b); 1301 1302 //now = system_time(); 1303 BRect touched = fPainter->DrawString(string, length, pt, delta, 1304 &cacheReference); 1305 //printf("drawing string: %lld µs\n", system_time() - now); 1306 1307 _CopyToFront(touched); 1308 } 1309 1310 return penLocation; 1311 } 1312 1313 // StringWidth 1314 float 1315 DrawingEngine::StringWidth(const char* string, int32 length, 1316 escapement_delta* delta) 1317 { 1318 return fPainter->StringWidth(string, length, delta); 1319 } 1320 1321 // StringWidth 1322 float 1323 DrawingEngine::StringWidth(const char* string, int32 length, 1324 const ServerFont& font, escapement_delta* delta) 1325 { 1326 return font.StringWidth(string, length, delta); 1327 } 1328 1329 // #pragma mark - 1330 1331 // DumpToBitmap 1332 ServerBitmap* 1333 DrawingEngine::DumpToBitmap() 1334 { 1335 return NULL; 1336 } 1337 1338 status_t 1339 DrawingEngine::ReadBitmap(ServerBitmap *bitmap, bool drawCursor, BRect bounds) 1340 { 1341 CRASH_IF_NOT_EXCLUSIVE_LOCKED 1342 1343 RenderingBuffer *buffer = fGraphicsCard->FrontBuffer(); 1344 if (!buffer) 1345 return B_ERROR; 1346 1347 BRect clip(0, 0, buffer->Width() - 1, buffer->Height() - 1); 1348 bounds = bounds & clip; 1349 AutoFloatingOverlaysHider _(fGraphicsCard, bounds); 1350 1351 status_t result = bitmap->ImportBits(buffer->Bits(), buffer->BitsLength(), 1352 buffer->BytesPerRow(), buffer->ColorSpace(), 1353 bounds.LeftTop(), BPoint(0, 0), 1354 bounds.IntegerWidth() + 1, bounds.IntegerHeight() + 1); 1355 1356 if (drawCursor) { 1357 ServerCursorReference cursorRef = fGraphicsCard->Cursor(); 1358 ServerCursor* cursor = cursorRef.Cursor(); 1359 if (!cursor) 1360 return result; 1361 int32 cursorWidth = cursor->Width(); 1362 int32 cursorHeight = cursor->Height(); 1363 1364 BPoint cursorPosition = fGraphicsCard->CursorPosition(); 1365 cursorPosition -= bounds.LeftTop() + cursor->GetHotSpot(); 1366 1367 BBitmap cursorArea(BRect(0, 0, cursorWidth - 1, cursorHeight - 1), 1368 B_BITMAP_NO_SERVER_LINK, B_RGBA32); 1369 1370 cursorArea.ImportBits(bitmap->Bits(), bitmap->BitsLength(), 1371 bitmap->BytesPerRow(), bitmap->ColorSpace(), 1372 cursorPosition, BPoint(0, 0), 1373 cursorWidth, cursorHeight); 1374 1375 uint8 *bits = (uint8 *)cursorArea.Bits(); 1376 uint8 *cursorBits = (uint8 *)cursor->Bits(); 1377 for (int32 i = 0; i < cursorHeight; i++) { 1378 for (int32 j = 0; j < cursorWidth; j++) { 1379 uint8 alpha = 255 - cursorBits[3]; 1380 bits[0] = ((bits[0] * alpha) >> 8) + cursorBits[0]; 1381 bits[1] = ((bits[1] * alpha) >> 8) + cursorBits[1]; 1382 bits[2] = ((bits[2] * alpha) >> 8) + cursorBits[2]; 1383 cursorBits += 4; 1384 bits += 4; 1385 } 1386 } 1387 1388 bitmap->ImportBits(cursorArea.Bits(), cursorArea.BitsLength(), 1389 cursorArea.BytesPerRow(), cursorArea.ColorSpace(), 1390 BPoint(0, 0), cursorPosition, 1391 cursorWidth, cursorHeight); 1392 } 1393 1394 return result; 1395 } 1396 1397 // #pragma mark - 1398 1399 BRect 1400 DrawingEngine::CopyRect(BRect src, int32 xOffset, int32 yOffset) const 1401 { 1402 // TODO: assumes drawing buffer is 32 bits (which it currently always is) 1403 BRect dst; 1404 RenderingBuffer* buffer = fGraphicsCard->DrawingBuffer(); 1405 if (buffer) { 1406 BRect clip(0, 0, buffer->Width() - 1, buffer->Height() - 1); 1407 1408 dst = src; 1409 dst.OffsetBy(xOffset, yOffset); 1410 1411 if (clip.Intersects(src) && clip.Intersects(dst)) { 1412 uint32 bytesPerRow = buffer->BytesPerRow(); 1413 uint8* bits = (uint8*)buffer->Bits(); 1414 1415 // clip source rect 1416 src = src & clip; 1417 // clip dest rect 1418 dst = dst & clip; 1419 // move dest back over source and clip source to dest 1420 dst.OffsetBy(-xOffset, -yOffset); 1421 src = src & dst; 1422 1423 // calc offset in buffer 1424 bits += (int32)src.left * 4 + (int32)src.top * bytesPerRow; 1425 1426 uint32 width = src.IntegerWidth() + 1; 1427 uint32 height = src.IntegerHeight() + 1; 1428 1429 _CopyRect(bits, width, height, bytesPerRow, xOffset, yOffset); 1430 1431 // offset dest again, because it is return value 1432 dst.OffsetBy(xOffset, yOffset); 1433 } 1434 } 1435 return dst; 1436 } 1437 1438 1439 void 1440 DrawingEngine::_CopyRect(uint8* src, uint32 width, uint32 height, 1441 uint32 bytesPerRow, int32 xOffset, int32 yOffset) const 1442 { 1443 // TODO: assumes drawing buffer is 32 bits (which it currently always is) 1444 int32 xIncrement; 1445 int32 yIncrement; 1446 1447 if (yOffset == 0 && xOffset > 0) { 1448 // copy from right to left 1449 xIncrement = -1; 1450 src += (width - 1) * 4; 1451 } else { 1452 // copy from left to right 1453 xIncrement = 1; 1454 } 1455 1456 if (yOffset > 0) { 1457 // copy from bottom to top 1458 yIncrement = -bytesPerRow; 1459 src += (height - 1) * bytesPerRow; 1460 } else { 1461 // copy from top to bottom 1462 yIncrement = bytesPerRow; 1463 } 1464 1465 uint8* dst = src + yOffset * bytesPerRow + xOffset * 4; 1466 1467 if (xIncrement == 1) { 1468 uint8 tmpBuffer[width * 4]; 1469 for (uint32 y = 0; y < height; y++) { 1470 // NOTE: read into temporary scanline buffer, 1471 // avoid memcpy because it might be graphics card memory 1472 gfxcpy32(tmpBuffer, src, width * 4); 1473 // write back temporary scanline buffer 1474 // NOTE: **don't read and write over the PCI bus 1475 // at the same time** 1476 memcpy(dst, tmpBuffer, width * 4); 1477 // NOTE: this (instead of the two pass copy above) might 1478 // speed up QEMU -> ?!? (would depend on how it emulates 1479 // the PCI bus...) 1480 // TODO: would be nice if we actually knew 1481 // if we're operating in graphics memory or main memory... 1482 //memcpy(dst, src, width * 4); 1483 src += yIncrement; 1484 dst += yIncrement; 1485 } 1486 } else { 1487 for (uint32 y = 0; y < height; y++) { 1488 uint32* srcHandle = (uint32*)src; 1489 uint32* dstHandle = (uint32*)dst; 1490 for (uint32 x = 0; x < width; x++) { 1491 *dstHandle = *srcHandle; 1492 srcHandle += xIncrement; 1493 dstHandle += xIncrement; 1494 } 1495 src += yIncrement; 1496 dst += yIncrement; 1497 } 1498 } 1499 } 1500 1501 1502 inline void 1503 DrawingEngine::_CopyToFront(const BRect& frame) 1504 { 1505 if (fCopyToFront) 1506 fGraphicsCard->Invalidate(frame); 1507 } 1508 1509 1510