Performance Tips
April 3, 2001
by Werner Sharp
In five years as an engineer on the Macromedia Director team, I did a lot of performance tuning, trying to make Director as fast as possible. With every release, we tried to make playback as fast or faster than it had been in the previous version. That was sometimes difficult with the new features we added but we tried our best. I'd like to share some tips I've learned over the years -- I hope you find them useful.
PrepareFrame vs. EnterFrame
There are three frame events (prepareFrame, enterFrame, and exitFrame). It turns out that the same script in a prepareFrame handler is slower than in enterFrame handler when setting sprite properties. My example movie below uses two simple scripts. The first, is in a prepareFrame handler:
on prepareFrame me
sprite (me.spriteNum).locH = me.spriteNum + the frame
end
The second is an enterFrame handler:
on enterFrame me
sprite (me.spriteNum).locH = me.spriteNum + the frame
end
Both have identical Lingo, but the prepareFrame handler runs about 10% slower. In the first example movie, pressing the slow button uses the prepareFrame script with 40 sprites and gets around 214 fps on my P3/800 Mhz laptop. The fast button runs the enterFrame version and achieves 232 fps.
View the prepareFrame vs. enterFrame movie.
The prepareFrame version is slower when setting sprite properties because it runs at a critical time in Director's animation engine. The Score data for the current frame has been set, but the sprites have not been drawn. If a sprite property is changed, it is updated when the sprites are drawn. For every change, there's an extra step to ensure the sprite is drawn correctly. For example, if a sprite is unchanged and it's locH is 50, the animation engine will not redraw the sprite. On a prepareFrame event, if the sprite's locH is changed to 100, the engine will be notified that it needs to draw the sprite immediately. For enterFrame and exitFrame events, the engine is not in the middle of drawing sprites so there's no extra step.
One script vs. many
Extending the previous example, I've changed the movie to use one script that modifies 40 sprites instead of 40 scripts that modify their own sprite. I can now achieve 260 fps.
View the one script vs. many movie.
While the object-oriented nature of Director is very powerful, combining scripts gets better performance. There's enough overhead in starting to run a Lingo script that having fewer scripts is faster. In my example movie, one script modifies 40 sprites:
on enterFrame me
i = 10
repeat while (i <= 50)
sprite (i).locH = i + the frame
i = i + 1
end repeat
end
This is a simple example just to demonstrate the performance gain. But this should be applicable in a real world situation. If you were writing an Asteroids clone that had a number of asteroid sprites, it would be faster to have one script that managed all the asteroid sprites as opposed to each sprite having its own custom behavior.
Setting the locZ
With the introduction of locZ in Director 7, you could dynamically alter the layering of sprites. This powerful feature decouples the sprite channels from their screen z-order. However, there is a performance problem with this feature that you should know about. When you set the locZ of a sprite to any value, the sprite automatically redraws itself. This is true even if you're setting the locZ equal to its current value.
This example movie's slow case simply sets the locZ of 3 sprites to the same value on every frame:
property myLocZ
on beginSprite me
myLocZ = 100
sprite (me.spriteNum).locZ = myLocZ
end
on enterFrame me
sprite (me.spriteNum).locZ = myLocZ
end
The fast case first checks the locZ and then only sets it if it's different:
property myLocZ
on beginSprite me
myLocZ = 100
sprite (me.spriteNum).locZ = myLocZ
end
on enterFrame me
if (sprite (me.spriteNum).locZ <> myLocZ) then
sprite (me.spriteNum).locZ = myLocZ
end if
end
Again, this simple example demonstrates the performance problem. If you were doing dynamic z-ordering to simulate 3D though, you might be updating the locZ for a range of sprites on every frame. In this case, it would be much faster to compare values before setting the property.
View the setting the locZ movie.
Setting the quad
It turns out the setting the quad has the same performance problem as setting the locZ. Every time you set it, even if it's the same value, the sprite redraws itself. This is probably less of an issue than the locZ problem, but affects performance dramatically since rendering quads is slow.
View the setting the quad movie.
Why do locZ and quad have this problem? I think it was just an oversight during the development of the feature. The rest of the sprite properties do not have this flaw. That's because they're stored in the Score and Director's animation engine only looks for differences from frame to frame for scored properties. Since locZ and quad are not stored in the Score but are runtime-only properties, they go through a different mechanism internally.
Member's image vs. Lingo image
Imaging lingo was added in D8 and allows the Lingo programmer direct access to the rendering engine of Director. You can create bitmap members at runtime using a variety of techniques. One pitfall of which to be aware is drawing directly to the image of a member. When a member is modified in any way, the Director animation engine looks for any sprites that refer to that member and marks them as needing to be updated on the next frame. This can significantly slow performance, especially with complex Lingo. In the following example, I draw a set of random squares to an image. In the slow case, I'm drawing directly to the member with 480 calls to fill. Each one of these calls is dirtying the member and walking each sprite to see if it is using that member.
View the imaging Lingo movie.
The slow case always draws directly to a member's image:
on enterFrame me
mImage = sprite (me.spriteNum).member.image
y = 0
repeat while (y < mImage.height)
x = 0
repeat while (x < mImage.width)
mImage.fill (x, y, x + 10, y + 10, paletteIndex (random (255)))
x = x + 10
end repeat
y = y + 10
end repeat
end
In the fast case, I used a cached image that I create in the beginSprite handler. I call fill using this cached image and then as a last step, set this image as the member's image. The last step duplicates the image into the member. Even though this script has more code running, it is faster because it doesn't have to dirty the member on every fill. On my machine, the speed-up for this change is from 19 fps to 76 fps.
property mImage
on beginSprite me
mImage = sprite (me.spriteNum).member.image.duplicate ()
end
on enterFrame me
y = 0
repeat while (y < mImage.height)
x = 0
repeat while (x < mImage.width)
mImage.fill (x, y, x + 10, y + 10, paletteIndex(random (255)))
x = x + 10
end repeat
y = y + 10
end repeat
sprite(me.spriteNum).member.image = mImage
end
One downside of this approach is that I'm allocating an image object on beginSprite. This slows beginSprite down some and requires more memory. But notice that I'm not allocating an image object on every enterFrame. Doing this would be slower than having one created on beginSprite and cached away. If I didn't cache the image object but created it on every frame, I only got about 51 fps. It's still much faster than the original version, but not as fast as our optimized version. This technique is only beneficial if you're doing a lot of imaging lingo on a member's image. If you were just doing one operation, it would be faster to simply draw directly to the member. For operations that require access to each pixel through getPixel and setPixel, using a cached image object can be more than ten times faster.
Director can be a powerful tool and has a vast feature set. Unfortunately, learning each feature and its quirks take time and a lot of experience. My advice to is to experiment and try different techniques. If something runs very slowly, there's probably another technique that will be much faster. Good luck with all your projects!
Sample Director 8 movies are available for download in Macintosh or Windows format.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.