Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot. Performance: Invalidating & drawing a lot.
Performance: Invalidating & drawing a lot.
Go Back  Xtreme Visual Basic Talk > > > Performance: Invalidating & drawing a lot.


Reply
 
Thread Tools Display Modes
  #1  
Old 01-18-2011, 01:23 AM
WhatsMyUsername's Avatar
WhatsMyUsername WhatsMyUsername is offline
Regular
 
Join Date: Jan 2011
Location: Currently? Costa Rica
Posts: 52
Default Performance: Invalidating & drawing a lot.


I knew I would hit this problem beforehand so I am not that worried >__>

As you awesome people already know, I am still quite new to this Graphics and drawing and invalidating thing.

I have improved, that I can tell. I won't explain everything, so here's the essential:

Technically, my application will let you draw a rectangle on the form. You click, drag, and boom, a red rectangle is there. This rectangle will disappear when you start drawing another.

I got that working (ha, you were not expecting that ;D)

But my problem is performance. You see, obviously I have to update, invalidate and paint my rectangle every time the mouse moves while it is in "drawing mode". I do that every time the mouse's position changes.

And that is quite a big deal. Every time the mouse's position changes? That's a lot of invalidations and redraws for a decently-sized rectangle!

I thought of skipping some mouse movement, like only update every 5 coordinates moved or something. But that would render my goal quite inaccurate.

What do you say?

I already invalidate only the regions necessary, that I can tell.

Maybe it is just my horrible code. Can someone post a small example?

Thanks again ~
Reply With Quote
  #2  
Old 01-18-2011, 09:08 AM
AtmaWeapon's Avatar
AtmaWeaponPerformance: Invalidating & drawing a lot. AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Try a timer instead, and redraw only a couple of times per second. Suppose "tmrRedraw" is a timer with a 500ms interval. Start the timer on mouse down, have the Tick handler call Invalidate(), and stop the timer on mouse up. An interval faster than 250ms is probably overkill. My gut feeling is the longer the interval, the less flicker you'll see.

You could also make sure your drawing target is double-buffered. For a form you can set the DoubleBuffered property to True; for a control it's a little bit more involved.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #3  
Old 01-18-2011, 12:39 PM
WhatsMyUsername's Avatar
WhatsMyUsername WhatsMyUsername is offline
Regular
 
Join Date: Jan 2011
Location: Currently? Costa Rica
Posts: 52
Default

Thanks, I got it working much better thanks to the timer.

Actually, it works fine, but after drawing a couple rectangles (dunno, 10 maybe?), the application starts to... slow down, it stops drawing correctly, it seems to take much longer to draw, and it isn't always accurate....

Here's a bunch of messy code, I was just testing, trying to get it working first:

Code:
Public Class Form1
    Dim p1 As Point
    Dim p2 As Point
    Dim t As Timer
    Dim rect As Rectangle
    Dim drawing As Boolean = False
    Dim startPointSet As Boolean = False
    Dim r As Region
    Dim semiTransparentRed As Color = Color.FromArgb(128, 255, 0, 0)

    Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Dim g As Graphics = e.Graphics
        g.FillRectangle(Brushes.BlueViolet, New Rectangle(0, 0, 256, 512))
        g.DrawImage(Image.FromFile("256x512Image"), New Point(0, 0))
        Using redBrush As New SolidBrush(semiTransparentRed)
            e.Graphics.FillRectangle(redBrush, rect)
        End Using
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        t = New Timer
        t.Interval = 100
        AddHandler t.Tick, AddressOf TimerTicker
    End Sub

    Private Sub TimerTicker(ByVal sender As Object, ByVal e As EventArgs)
        r = New Region(rect)
        p1.X -= p1.X Mod 32
        p1.Y -= p1.Y Mod 32
        p2.X -= p2.X Mod 32
        p2.Y -= p2.Y Mod 32
        rect = New Rectangle(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y)
        r.Union(rect)
        Me.Invalidate(r)
    End Sub

    Private Sub Form1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
        If Me.PointToClient(Cursor.Position).X < 256 Then
            drawing = True
            If startPointSet = False Then
                t.Start()
                startPointSet = True
                p1 = getMousePoint()
            End If
        End If
    End Sub

    Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
        drawing = False
        startPointSet = False
        t.Stop()
    End Sub

    Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
        If drawing Then
            p2 = getMousePoint()
        End If
    End Sub

    Private Function getMousePoint()
        Return Me.PointToClient(Cursor.Position)
    End Function
End Class

Note: following my goal of a "32x32 tile selector", you see I am only drawing rectangles with dimensions that are multiples of 32.
Reply With Quote
  #4  
Old 01-18-2011, 01:18 PM
AtmaWeapon's Avatar
AtmaWeaponPerformance: Invalidating &amp; drawing a lot. AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

I'd imagine the biggest problem is you're loading an image in your paint loop. Image.FromFile() has to open a file and read all of its bytes into memory. This is particularly expensive if it's in a format that requires decompression like JPG. It's best to do as little work as possible in your paint loop, and cache any expensive resources so you don't have to reload them.

There's a small problem slightly unrelated. You heard about invalidating regions rather than the entire screen, and it sounded like a good idea. However, this is an advanced case of painting a form for high-performance scenarios. It's more complicated to invalidate *just* a region and draw *just* the invalidated region. You should start writing your application without it and only use it when you know this is the performance bottleneck. Never start with the hard code.

Right now, your calculation of the invalid region isn't getting you anything. You go to all the work to invalidate a particular rectangle, but you don't pay it any heed in the Paint event handler and redraw the entire screen. Why calculate the invalid region if you're going to redraw the entire screen?

The first thing I tried was the most successful: I set the form's DoubleBuffered property to True. I believe flicker is caused because your eye perceives pieces of the individual drawing instructions. When the form is double-buffered, all of your painting happens on a bitmap which is then drawn to the screen all at once. This is almost always the solution to flicker.

I also modified your Paint handler to use a private variable _backgroundImage instead of loading the image every render cycle. Sure, Image implements IDisposable and I said you should dispose of them quickly, but in this case you're going to be using the image as long as the application is open, so it makes no sense to release it, at least not until the form is going to close.

I think 100ms might be a bit too fast for the timer, but it works. Seeing as the mouse has to move ~32 pixels before the rectangle's size will change, someone would have to be pretty quick to complain it's not responsive.

Here's the application with the changes I made; I left the region calculation in for no particular reason:
Code:
Public Class Form1
    Private p1 As Point
    Private p2 As Point
    Private t As Timer
    Private rect As Rectangle
    Private drawing As Boolean = False
    Private startPointSet As Boolean = False
    Private r As Region
    Private semiTransparentRed As Color = Color.FromArgb(128, 255, 0, 0)

    Private _backgroundImage As Image = Image.FromFile("256x512Image.png")

    Public Sub New()
        InitializeComponent()

        ' You could also set this in the designer
        DoubleBuffered = True
    End Sub

    Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Dim g As Graphics = e.Graphics
        g.FillRectangle(Brushes.BlueViolet, New Rectangle(0, 0, 256, 512))
        g.DrawImage(_backgroundImage, New Point(0, 0))
        Using redBrush As New SolidBrush(semiTransparentRed)
            e.Graphics.FillRectangle(redBrush, rect)
        End Using
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        t = New Timer
        t.Interval = 250
        AddHandler t.Tick, AddressOf TimerTicker
    End Sub

    Private Sub TimerTicker(ByVal sender As Object, ByVal e As EventArgs)
        r = New Region(rect)
        p1.X -= p1.X Mod 32
        p1.Y -= p1.Y Mod 32
        p2.X -= p2.X Mod 32
        p2.Y -= p2.Y Mod 32
        rect = New Rectangle(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y)
        r.Union(rect)
        Me.Invalidate(r)
    End Sub

    Private Sub Form1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
        If Me.PointToClient(Cursor.Position).X < 256 Then
            drawing = True
            If startPointSet = False Then
                t.Start()
                startPointSet = True
                p1 = getMousePoint()
            End If
        End If
    End Sub

    Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
        drawing = False
        startPointSet = False
        t.Stop()
    End Sub

    Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
        If drawing Then
            p2 = getMousePoint()
        End If
    End Sub

    Private Function getMousePoint() As Point
        Return Me.PointToClient(Cursor.Position)
    End Function
End Class
I want to make some suggestions about coding style and general practices, but don't want to overwhelm you in one post. My next post in this thread will contain suggestions and a revised edition of the form, but has nothing to do with answering this question. (This kind of stuff makes half of people mad at me and claim it doesn't matter; I don't care to argue about that.)
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
  #5  
Old 01-18-2011, 02:00 PM
WhatsMyUsername's Avatar
WhatsMyUsername WhatsMyUsername is offline
Regular
 
Join Date: Jan 2011
Location: Currently? Costa Rica
Posts: 52
Default

.... yes, it was the image being drawn what caused the problem =/
So only invalidate specific regions if I really need to....

Quote:
Originally Posted by AtmaWeapon View Post
I want to make some suggestions about coding style and general practices, but don't want to overwhelm you in one post. My next post in this thread will contain suggestions and a revised edition of the form, but has nothing to do with answering this question. (This kind of stuff makes half of people mad at me and claim it doesn't matter; I don't care to argue about that.)
Oh no, feel free to do so, I welcome all suggestions, tips and feedback! I know my code is incredibly messy and weird (for now all I care about is "have the **** thing working at least"), VB definitely isn't my favorite language.
Reply With Quote
  #6  
Old 01-18-2011, 02:09 PM
AtmaWeapon's Avatar
AtmaWeaponPerformance: Invalidating &amp; drawing a lot. AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default

Now for the unrelated stuff; it's mostly coding style stuff. Many people respond to these kinds of suggestions as time wasters or not important. I feel like they're important. Think about it this way: the only reason we can talk to each other is we both speak English and understand enough grammar and style to communicate our ideas. IF I sTarT,,, breakign. ths rools it gets harder to understand what I mean. So, too, can program code benefit from standard practices. If we all use the same rules, it's easier to understand each other's code.

Feel free to disagree, but I *will* point out when failure to follow one of these practices causes a problem in the future There's one exception to this rule: if you have a boss, teacher, or other authority figure that tells you to follow different rules, follow their rules. You can complain about them and argue for using Microsoft's rules, but don't fail a class or lose a job because of these rules.

I normally rant on for pages; this time I'm instead going to say for naming and capitalization read Guidelines for Names. These aren't rules put out by some crackpot forums dictator, they're the rules MS made after designing .NET 1.0 and they follow these rules themselves. Whether you follow those conventions or not, be consistent. If you have a method getFoo(), don't have a property GetFoo that does something different. Figure out how you want to name things (preferably the MS way) and do it the same way every time. It will help you and help anyone else that sees your code.

There's a lot more to read; that's just a section of Design Guidelines for Developing Class Libraries. Read through it one day; there's lots of good stuff in there. You wouldn't think so, but you can learn a lot from it.

Now let's assume you've read a little bit and I can talk directly about what in your code prompted this discussion.

Principle of least privilege
This principle states that any programming element should only be able to touch what it needs to do its job. You've got some variables at the class level that represent things only used in one method. For example, r is a Region that's only used in TimerTicker(). Since it gets passed to Invalidate, it ends up as part of the PaintEventArgs passed to the Paint handler. When you make a variable part of the class, you are making an implication that the bulk of the class's functionality needs access to the object. Every class-level variable is a detail someone reading the code has to have in their head at all times; make as few as possible. Use local variables for things that don't need to be shared between several methods.

Use names that describe a variable's purpose
It's tempting to use a name like rect for a rectangle. While it's clear the variable is a rectangle, it's not clear *what* it does. It's only after reading the code that I realize this is the rectangle that represents the selection the user is making. _selectionRectangle would be a much better name. "But that's got so many more characters!" is the common argument against this suggestion. You have to type it once, then Intellisense kicks in. If you type it 10 times, you're going to read it 1,000 times. Every 0.1 second you save reading is worth 10 seconds spent typing. Longer variable names let you figure out what they do as you read them, rather than pausing to think about it.

That was actually it. Here it is with new names, local variables, and the region calculation cut out. See if it doesn't look better to you:
Code:
Public Class Form1
    Private _backgroundImage As Image = Image.FromFile("256x512Image.png")
    Private _endPoint As Point
    Private _isSelecting As Boolean
    Private _redrawTimer As Timer
    Private _selectionRectangle As Rectangle
    Private _startPoint As Point

    ' This is equivalent to a constant for reference types. It's easier to remember
    ' "Try Const, then Shared ReadOnly" than it is to explain why there's a difference.
    Private Shared ReadOnly SemiTransparentRed As Color = Color.FromArgb(128, 255, 0, 0)

    Public Sub New()
        InitializeComponent()

        ' You could also set this in the designer
        DoubleBuffered = True
    End Sub

    Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Dim g As Graphics = e.Graphics
        Dim imageBounds As New Rectangle(0, 0, _backgroundImage.Width, _backgroundImage.Height)
        g.FillRectangle(Brushes.BlueViolet, imageBounds)
        g.DrawImage(_backgroundImage, 0, 0)
        Using redBrush As New SolidBrush(semiTransparentRed)
            e.Graphics.FillRectangle(redBrush, _selectionRectangle)
        End Using
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        _redrawTimer = New Timer
        _redrawTimer.Interval = 250
        AddHandler _redrawTimer.Tick, AddressOf TimerTicker
    End Sub

    Private Sub TimerTicker(ByVal sender As Object, ByVal e As EventArgs)
        _startPoint.X -= _startPoint.X Mod 32
        _startPoint.Y -= _startPoint.Y Mod 32
        _endPoint.X -= _endPoint.X Mod 32
        _endPoint.Y -= _endPoint.Y Mod 32
        _selectionRectangle = New Rectangle(_startPoint.X, _startPoint.Y, _endPoint.X - _startPoint.X, _endPoint.Y - _startPoint.Y)

        Me.Invalidate()
    End Sub

    Private Sub Form1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
        ' I eliminated the startPointSet variable; _isSelecting should be sufficient. If we get in
        ' a state where the mouse goes down and _isSelecting is true, it's probably best to just start over.
        ' There's probably some stuff that needs to be done with mouse capture, but that's another topic.
        Dim clickPoint As Point = GetClientMousePosition()
        If clickPoint.X < 256 Then
            _isSelecting = True
            _redrawTimer.Start()
            _startPoint = clickPoint
        End If
    End Sub

    Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseUp
        _isSelecting = False
        _redrawTimer.Stop()
    End Sub

    Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
        If _isSelecting Then
            _endPoint = GetClientMousePosition()
        End If
    End Sub

    Private Function GetClientMousePosition() As Point
        Return Me.PointToClient(Cursor.Position)
    End Function
End Class
Note that if you're in "Just make the @*#! thing work" mode it's acceptable to let some of these guidelines lapse, but I have found that over time taking shortcuts in prototype code leads to me making mistakes that cost a lot of time to fix. Being in less of a hurry the first time doesn't tend to have that problem.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump

Advertisement:





Free Publications
The ASP.NET 2.0 Anthology
101 Essential Tips, Tricks & Hacks - Free 156 Page Preview. Learn the most practical features and best approaches for ASP.NET.
subscribe
Programmers Heaven C# School Book -Free 338 Page eBook
The Programmers Heaven C# School book covers the .NET framework and the C# language.
subscribe
Build Your Own ASP.NET 3.5 Web Site Using C# & VB, 3rd Edition - Free 219 Page Preview!
This comprehensive step-by-step guide will help get your database-driven ASP.NET web site up and running in no time..
subscribe
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot. Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
 
Performance: Invalidating &amp; drawing a lot.
Performance: Invalidating &amp; drawing a lot.
 
-->