Drawing Invalidation Question

  • When inside of -[NSView drawRect:], what's the difference between

    - (void)drawRect:(NSRect)rect;
    {
          if ([self needsToDrawRect:someRect])
              ....
    }

    ... and ...

    - (void)drawRect:(NSRect)rect;
    {
          if (NSIntersectsRect(rect, someRect))
              ....
    }

    ... ?

    AFAICT from the documentation, there isn't a difference. Am I
    misreading something important?

    --
    Seth Willits
  • On 1 Feb 2009, at 05:41:49, Seth Willits wrote:

    > When inside of -[NSView drawRect:], what's the difference between
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if ([self needsToDrawRect:someRect])
    > ....
    > }
    >
    >
    > ... and ...
    >
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if (NSIntersectsRect(rect, someRect))
    > ....
    > }
    >
    >
    > ... ?

    Apart from anything else, the first makes it clear what your intent
    is, which would be useful when you return to that bit of code later.
  • > From the View Programming Guide for Cocoa (
    http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/CocoaViewsGuide/O
    ptimizing/chapter_8_section_5.html#//apple_ref/doc/uid/TP40002978-CH11-SW4

    ):

    "In Mac OS X version 10.3 and later, views can constrain their drawing
    even further by using the NSView methods getRectsBeingDrawn:count: and
    needsToDrawRect:. These methods provide direct and indirect access,
    respectively, to the detailed representation of a view's invalid
    areas—that is, its list of non-overlapping rectangles—that the
    Application Kit maintains for each NSView instance. The Application
    Kit automatically enforces clipping to this list of rectangles, and
    you can further improve performance in views that do complex or
    expensive drawing by having them limit their drawing to objects that
    intersect any of the rectangles in this list."

    The parameter to -drawRect: specifies a rectangle that includes all
    those dirty rectangles.  If, for example, your view consists of lots
    of expensive-to-draw regions, you can test each of those regions using
    -needsToDrawRect: to avoid drawing ones that aren't in a dirty region
    but wind up being included in the big rectangle.

    --Kyle Sluder
  • > When inside of -[NSView drawRect:], what's the difference between
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if ([self needsToDrawRect:someRect])
    > ....
    > }
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if (NSIntersectsRect(rect, someRect))
    > ....
    > }

    The NSView keeps a list of the actual subrectangles inside its bounds
    which are dirty, but it unions them all together into the single
    'rect' parameter in -drawRect:, so 'rect' can be much larger than the
    strict minimum of what needs to be drawn.

    You can get the actual list of dirty rects with -
    getRectsBeingDrawn:... or, alternately, if you have an expensive
    element you can check ahead of time if you need to draw it at all with
    -needsToDrawRect: -- even though the element might lie inside your
    bounds and inside 'rect', it still might not be touched by a dirty
    region.

    -W
  • On 1 Feb 2009, at 4:41 pm, Seth Willits wrote:

    >
    >
    > When inside of -[NSView drawRect:], what's the difference between
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if ([self needsToDrawRect:someRect])
    > ....
    > }
    >
    >
    > ... and ...
    >
    >
    > - (void)drawRect:(NSRect)rect;
    > {
    > if (NSIntersectsRect(rect, someRect))
    > ....
    > }
    >
    >
    > ... ?
    >
    >
    > AFAICT from the documentation, there isn't a difference. Am I
    > misreading something important?

    As others have mentioned, there is potentially a huge difference. If
    you invalidate a lot of small rectangles, the update region might be
    quite complex in shape; the <rect> passed in -drawRect: is only the
    bounds of this complex area.

    But there is also a difference in intent between -needsToDrawRect: and
    -getRectsBeingDrawn:count:, even though they also appear to amount to
    much the same thing. The first method implies you'll be iterating a
    list and testing each element against the view to see if it needs to
    be drawn or not. In many (most?) cases that's going to work out just
    fine. But if you have a huge number of possible objects to draw,
    merely iterating the list and testing for every update could incur a
    noticeable performance penalty. The second method offers the
    possibility of using a different approach that allows you to
    efficiently search and locate the objects needing to be drawn without
    iterating the entire list to do so. (An analogy would be the
    difference between a linear search and a binary search). That can have
    great benefits (though as I say, typically only for large numbers of
    objects). Algorithms such as BSP, R*-Trees and so forth can be used
    with the second approach. My own tests indicate that the number at
    which a BSP storage approach starts being worth considering is around
    the 1000 object mark, depending on what exactly it is you're drawing.
    YMMV.

    This is probably more than you need or care to know at this point but
    I thought it might be worth mentioning ;-)

    --Graham
  • On Feb 1, 2009, at 3:07 AM, Graham Cox wrote:

    > As others have mentioned, there is potentially a huge difference. If
    > you invalidate a lot of small rectangles, the update region might be
    > quite complex in shape; the <rect> passed in -drawRect: is only the
    > bounds of this complex area.

    Which is why the docs confused me. I knew there were subrectangles,
    but the weird thing was this line in the docs:
    "It is optimized to efficiently reject any rectangle that lies outside
    the bounding box of the area the receiver is being asked to draw in
    drawRect:."

    So it's not optimized to reject anything outside of the sub-
    rectangles? This sentence really threw me off. In retrospect and I can
    see that it simply means that it's doing a simple check up front like
    below, but it's a rather poorly worded description as it doesn't
    mention the sub-rectangles at all. Documentation fail. ;-)

    - (BOOL)needsDisplayInRect:(NSRect)rect;
    {
    if (!NSIntersectsRect(rect, theBigBoundingRect)) {
      return NO;
    }

    ... other checks
    }

    > But there is also a difference in intent between....
    > This is probably more than you need or care to know at this point
    > but I thought it might be worth mentioning ;-)

    Actually, it is very relevant to what I am doing. :-)

    Thanks all,

    --
    Seth Willits
  • On 2 Feb 2009, at 8:05 am, Seth Willits wrote:

    > So it's not optimized to reject anything outside of the sub-
    > rectangles? This sentence really threw me off. In retrospect and I
    > can see that it simply means that it's doing a simple check up front
    > like below, but it's a rather poorly worded description as it
    > doesn't mention the sub-rectangles at all. Documentation fail. ;-)

    I agree it's a long-winded way to say it's performing a basic
    intersection check, but I think it's probably historic - the
    subrectangles weren't available until 10.3 (I think, maybe it was
    10.2?) so previous to that you only had the overall bounding box to go
    on. The docs may not have been revised since then.

    --Graham
  • On Sun, Feb 1, 2009 at 4:05 PM, Seth Willits <slists...> wrote:
    > On Feb 1, 2009, at 3:07 AM, Graham Cox wrote:
    >
    >> As others have mentioned, there is potentially a huge difference. If you
    >> invalidate a lot of small rectangles, the update region might be quite
    >> complex in shape; the <rect> passed in -drawRect: is only the bounds of this
    >> complex area.
    >
    > Which is why the docs confused me. I knew there were subrectangles, but the
    > weird thing was this line in the docs:
    > "It is optimized to efficiently reject any rectangle that lies outside the
    > bounding box of the area the receiver is being asked to draw in drawRect:."
    >
    > So it's not optimized to reject anything outside of the sub-rectangles? This
    > sentence really threw me off. In retrospect and I can see that it simply
    > means that it's doing a simple check up front like below, but it's a rather
    > poorly worded description as it doesn't mention the sub-rectangles at all.
    > Documentation fail. ;-)

    You left out the word "efficiently" from the formulation of your
    contrapositive, which I think is important. To me, the words
    "optimized" and "efficiently" make it clear that it's only describing
    a speed enhancement, and telling you that there's no need to do your
    own bounding-rect culling beforehand. The full behavior of the method
    is described at the top of the documentation.

    Of course documentation quality and clarity is a matter of opinion,
    and may vary.

    Mike