Be Developers' Conference Fast and Easy Interface Code George Hoffman |
Mr. Hoffman: This is a short one, but we're already behind, so the things I'm going to be talking about are little more pedestrian topics.
Those of you who were here for the last session, Bdirect Window, and all the demos that Pierre hooked up for it were pretty sexy in more ways than one, as you saw.
This is a little more mainstream application development in terms of what do you do with the rest of your application to make its performance match the media components. How do you use the interface kit to get the maximum performance and maximum user responsiveness out of your application and out of the system. So this is basically going to be a hodgepodge of little tips and tricks and I'm going to have to rush through it it looks like, but that's okay.
So just going to talk about some general themes that you should keep in mind while you're writing your lovely interface kit code. These are more motives to talk about while you're writing just to keep you in the right frame of mind, then a couple specifics that are new or improved in R3. Using transparency backgrounds and making use of some new shortcuts, then some more specific tips on coding for responsiveness, just so you get that snappy feeling that we all like to have out of our applications and then just a review of some preexisting tricks that have been in there in PR2 and before.
Some things to keep in mind, for those of you who have been coding on the BeOS and coding interface kit code, this should come as no surprise that app_server transactions tend to be kind of costly. When I say app_server transactions tend to be kind of costly, it's pretty transparent when you're actually doing a transaction, because the client side caches all these commands that you send.
But basically when you send a images number of drawing commands or any drawing commands to the app_server, drawing lines, filling rectangles, those normal things like that, these get cached up into little packages and sent to the application server every once in a while or when you call a sync or a flush call.
Doing those transactions is costly because you have a thread context switch. You also have some memory copying that goes on to get the information over to the app_server. So it's good to keep in mind that clearly -- this should come as no surprise either -- the less number of calls that you have the better and there are some specific ways to decrease the number of commands, drawing commands and state changes that you do, and I'll talk about that in a little bit.
BViews take up resources that are not suitable for all purposes. Those of you who are experienced Be programmers will know that this is a topic around which much controversy has centered using BViews and being able to use many of them specifically in a window. They take up resources. Using 10,000 of them is not a good idea.
If you have a images Matrix of buttons that you need and each one is pretty much identical and they all do something pretty similar, there's no need to have each one be a different BView. BView is complex. It has a clipping region, it has a drawing context, including pen size and colors, fonts, definitions, everything like that. You simply don't need to make each one a BView in a lot of cases. So be careful and use your -- use your BViews intelligently.
The resources that I'm talking about are, of course, not only memory but the port that are used to communicate with the app_server, through resizing views and things like that, send status messages back through the port, resizing them from the client size, from the status size through the port to the app_server. That port is also a resource and having images numbers, excessive numbers of BViews hogs that resource.
The BWindow threads purpose is to be responsive. This is something that's easy to forget about because threading is so ubiquitous and sort of hard to get around in the BeOS. Anything that executes in your BWindow object, unless you do specific things to spawn other threads, executes in the BWindow thread.
The BWindow thread is calling at what we call, perhaps misleadingly, display priority. Display priority is a higher priority than the normal thread priority for the system. So if you're doing a lot of extra processing, if you have a lot of, for instance, some CPU-intensive graphics to draw, doing that in BWindow thread is going to hog the system and more importantly, it's going to make the BWindow a lot less responsive to user input. The user clicks, they'll have to wait for your code to finish going through whatever convolutions it's doing.
So it's probably a good idea to spawn another thread when you have something CPU-intensive things to do and keep your BWindow thread free to respond to user input, because that's what it's around there for. It's there to respond to the user.
And offscreen bitmaps are often not needed. This is also kind of a point of some contention. Reducing flicker is always a big issue when designing interfaces that look nice and respond nicely to the user, and using offscreen bitmaps is one way of eliminating flicker, of course, but it also can be a resource hog, hog a lot of memory. It can be -- it can be one extra step of indirection, don't always need it. Just some things to keep in mind, like I said.
This is a new thing in R3. There is a call SetViewColor, which sets the background color of your view. It's pretty simple. You expose the view through any means and the app_server will clear it for you in that color and then hand the view off to you to be displayed to the draw making gets called?
This is really handy when you have a view which has imagesly the same color background, which is often the case. However, sometimes you have, for instance, and I see here a web page with a title bitmap in the background. There is no constant color that you want to fill it with and you're just going to be copying over that memory anyway, so even though it's hardware accelerated, you want to avoid getting it filled.
So if you specified B_TRANSPARENT_32_BIT, which based on our tests a lot of you already do in SetViewColor, your view will never get touched by the app_server. The app_server will depend on you to fill it and this will reduce flicker quite a bit when redrawing and makes things look and feel a lot nicer.
The disadvantage is that it can cause what I would call NT-like window droppings. Those of you who have used NT 4.0 will know that when you take a window and drag it around on the screen quite a bit there's a lot of window droppings that occur, just parts -- parts of the screen where it doesn't get refreshed right away. So the old -- the old window is still there and still remaining over top of everything.
That kind of thing can get caused by this if your application, for instance, crashes and can't update anything anymore, or if it just takes a long time to draw and doesn't fill everything immediately before going through whatever CPU-intensive things it has, so it's something to watch out for.
Making use of shortcuts. A new -- another new feature in R3 is the state stack API. PushState and PopState. That's all there is to it.
Those of you who are familiar with State stacks will probably -- and like them, will be happy and pretty much understand exactly what I'm talking about. Those of you who haven't seen it, I have a little code segment that I'll show.
It makes interface out of ... it makes interface code more modular and faster. More modular, because things that are happening at lower levels of the state stack don't have to know about what has happened at upper layers and take it -- they take advantage of things like clipping and translation that happens at those layers. And faster, because you're not doing as many transactions to set and restore state if you need to, so because you can just push the state and then pop it when you're done.
Let me show you the code right now. Pretty simple example. So here we have a -- a view which in its drawing method makes two calls to this subdraw, passes a point and a clipping region, which here I'm not using, and as you can see, we have some temporary variables.
We save the old state, the color, the drawing mode and the font. We set up the new context, setting a new font and new drawing mode, and then we set up a new clipping region here and intersect it with our old clipping region, so that we're honoring any clipping region that has come before us. This is pretty important if you're doing -- if you're doing rehearsive calls down a complex drawing pipeline, as I know some of you are.
We set the color and we draw a string. Set the color, draw it again. It looks pretty boring and it's actually not even -- there we go. Pretty boring. However, quite of bit of code to do that simple thing and here's the state stack version. As you can see, it's quite a bit cleaner.
We have PushState has replaced our saving of parameters and down here PopState has replaced the restoration of parameters that we had at the end of this call. Then in the middle we don't have to clip -- we don't have to combine the clipping regions ourselves. We don't have to know that anyone else has done any clipping before us. That's all done by the state stack. The state stack has established a clipping context above us which we're using once we travel down.
The advantage of this that I see and that hopefully you see are that, well, for instance, there's less code, less lines of code. Those of you who get paid by lines of code are working for the wrong operating system I'm afraid. We like to reduce lines of codes. It's cleaner. I would argue that it's self-documenting in this case, whereas knowing what's going on up here requires some comments. Those of you who depend on that for job security also won't like us.
It's faster, potentially much faster, depending on how much optimization we do. That's another thing that's nice about it. It let's us take care of optimization issues and it's more extensible and I'll go back to the slide for that.
Open-ended extensibility. When we add more complex clipping regions, perhaps linear transformations, color correction, other things that we will add in the future, these things will take effect and be able to get propagated down the state stack hierarchy or hierarchy is probably the wrong word. Get propagated down the state stack and you don't have to know anything about these more complex clipping regions that are going on above you. You can change your own clipping region accordingly and it will get automatically combined with clipping regions above you. You can change your transformations, whatever. Right now this applies to translation for origins, clipping regions and scaling.
One thing to watch out for when you're using state stacks is don't query the drawing state unnecessarily after doing a push. This can be clear if you think of why it's faster, the state is being maintained on the server side. The stack is entirely on the server side. I'm sorry, that should be a pop rather.
Don't query the drawing state unnecessarily after doing a pop, because once you do a pop you've restored a state on the server side. The client side does not know what that new state is. That's the whole point; it doesn't have to. If you query it will have to go to the server and ask it, which sort of defeats the purpose, at least of the speed gates.
BPictures. This is very familiar to those of you who -- hopefully pretty familiar to those of you who have used the interface kit. They're nothing new. They have been around for quite awhile, but they have gone through big revamping in R3. They're a lot more easily accessible. They have an accessor method called play which plays them back on the client side, takes a list of callbacks. This is undocumented right now. It will -- it will be documented at some point -- oh, it's documented? Yeah, that's news to me. Did you look at the code?
Okay. So it's documented and therefore use it with abandon. Use them for static stuff that may be drawn often. If you -- if you have a view which is doing a lot of updates, do the same kind of thing. If you have something that you assume will be exposed a lot, basically, anything that's complex which would benefit from having less transactions. It's a single transaction, draw the picture. They can be archived maybe as a resource, so that's an advantage.
If you want to make your interface look a little different and you have your pictures in a resource, you can just replace those pictures and you don't have to change any code. And again, helps with code modularity, as well as performance.
Here's some specific tips on coding for responsiveness, the things that in my experience and in our code and in developer's code have tended to be things that could be improved. Use offscreen certainly as the last resort. That's perhaps a bit strong, but using -- using offscreen bitmaps can be unnecessary in a lot of cases.
It's difficult to say more without saying a lot more; however, there are a lot of facilities, hardware acceleration copy bits is a call which does screen-to-screen blitting. Doing updates in that way on-screen can reduce flicker enormously, to the point where it's unnoticeable and offscreens aren't necessary.
And offscreens, the reason I include this under responsiveness is although offscreens reduce flicker, they can cause the interface to seem a little less snappy, because more time has to be taken to draw an offscreen.
Make bitmaps the "right" color space. This is extremely important in R3, especially since we have expanded the color space a lot. You can now have big- and little-endian pixels in all the color spaces and we've also added 15 and 16 bitmaps, so that makes a total of six different, seven, eight, eight different color spaces, including endian uses that you can have things in.
If you just use our defaults constants that will create little-endian pixels in your bitmaps, which may be what you want if you're on Intel. For instance, you're blitting -- you want to blit to the screen quickly. If you're on Power PC however, if you want to blit quickly to the screen, you probably want to use big-endian pixels. That way we don't have to swap it and we can use faster blitting agents.
That can increase the speed of your blitting code quite a bit. The screen is good for some things. I mentioned this just a second ago in my tirade about bitmaps. Doing screen-to-screen blittings, which we provide facilities for, is quite fast and can work quite nicely. It's good at filling regions, good at drawing lines, good at a lot of things. Bitmaps aren't as good at these things.
We're planning on extending our hardware acceleration support to do DMA bitmaps, as Pierre talked about in his talk. DMA from bitmaps to the screen, the app_server can do this when blitting. Can't do that from memory to memory, so something to keep in mind.
Use the right drawing mode. I mostly mean the difference between copy mode and over mode. Those of you who have used the interface kit, B_OP_COPY and B_OP_OVER, the difference is not visually detectable if you're using "over" all the time it will work. Everything will work, it will just be quite a bit slower than it has to be in some cases.
For instance, when drawing antialias text we have to read from the frame buffer every time you're drawing text, if we use B_OP_OVER reading. Be sure to use B_OP_COPY whenever you can, it's the fastest mode. All the drawing, all the rendering code is going to be optimized to work in B_OP_COPY mode.
Don't Be afraid to forego message-passing. If you have, for instance, oh, I don't know, a tab view, which is modifying a columned list of items, for instance, and you -- you grab the -- grab the column view and you start -- you grab the tab header and start moving the columns and resizing the columns, you could update the other view, which is your column view and in a number of ways.
You could call invalidate on it, but invalidate will send a message to the app_server. The app_server will invalidate that in its data cache. Then it will wait for the update thread to get around to sending you an update message. Something will come back across the port and your viewer will get an update message and will then draw. This is not the most efficient thing to do.
Much better would be to, for instance, call a function method in your -- in this other view, which will erase the background, redraw the view, write in line. You don't have to worry about sending messages, you don't have to worry about changing thread contexts. It's perhaps a bit less clean, but the performance gains can be dramatic.
And use multiple threads without much guilt. I say "much" because there is a certain amount of resources associated with each thread, 16-KB kernel stack, as Dominic will tell you any time you mention spawning a thread in his presence, but spawning multiple threads can be a very good way of dealing -- of especially constructing a snappy interface or responsive interface.
If you have a lot of drawing to do you can spawn a thread, that will do it. Locking the window periodically to do its drawing if it's CPU-intensive. If you have an application for which you needed to track the mouse to do something and you're doing some kind of CPU-intensive thing at the same time, for instance, you could spawn a thread to track the mouse. Threads are lightweight and you shouldn't have to go to confession afterwards.
Preexisting tricks. These are things that have been around in PR2 and before. Begin/EndLineArray. This is a pretty handy little function if you want to draw a images number of lines. We use it a lot for gradient fills. Allows you to specify a list of lines and colors. You can -- it will use a shared memory buffer to store all these lines. Will send one transaction to the app_server quite a bit faster than doing a images number of draw line commands.
DrawBitmapAync, which some people may not know about. This is the regular DrawBitmap called will synchronize with the app_server after every blit. The intention is that if you change the bitmap directly after you do this DrawBitmap call, you don't want the app_server to finish, call that bitmap server to the screen already. So you might want to do a Sync afterwards to make sure you're synchronized up. DrawBitmapAsync does not draw that sync. You have to make sure you don't modify the bitmap until after this draw bitmap until you do another sync or flush.
FillRegion. Does what it says. Fills the region. Region object right now is composed of a list of rectangles. It's a very nice way of doing, for instance, if you're doing selective updates on a view, you can do a FillRegion and have a images number of rectangles in the view cleared for -- for your drawing. And Disable/EnableUpdates in view window, which hides from the interface coder up in BWindow, so you may not be aware of it. It simply tells the update thread that continually runs in the app_server to not send the window any updates until you call EnableUpdates again.
This can be useful if you're making a charge, number of changes and you don't want the window to get updated in between each one, you just want one update at the end. You can call Disable/Enable Updates, do all your changes and we'll all get refreshed in one atomic operation.
That's about it. I think I'm over already. Actually, just about right. I can take any questions.
Okay. I'm sorry. There's one question.
A Speaker: I was just wondering if you could compare the line array.
Mr. Hoffman: If I could compare the line array and the picture? They sort of serve similar purposes.
Well, the line array is a one-shot deal. You begin line array, add line, add, line, add line, end line array. There's nothing persistent going on there. It's just gotten rid of after its done. The picture is more intended for something that you'll keep around a lot and begin again and again. Begin line array could be a one-shot thing. It could be just any number of images number of lines you want to draw.
Anything else?
A Speaker: Would there be advantage to using a BPicture if all you're going to do is stroke a polygon with, say, a images number of points into that BPicture? Is there a savings in the picture claim that back over, as opposed to doing a transaction in the app_server?
Mr. Hoffman: No, because there's a certain amount of overhead in creating the picture, so if you're only going to use it once, there's no -- there's no reason, because the -- as it is currently, actually, when the picture is being created, each drawing command actually sends its normal transaction over the port.
The picture is exposed, in other words, on the server side. So if you say begin this picture, draw these points, you know, stroke this polygon and then close the picture, rather than just doing the stroke "polygon" you're actually going to add overhead, then you're going to have to add more overhead of drawing the picture once it's done and then deleting the picture.
So that would actually be a loss. For things like stroke polygon, that's up to us to stroke that in the shared memory. I think you were next.
A Speaker: The Disable/EnableUpdates function there at the end, once you enable updates, it's still going to have to make every one of those transactions, isn't it? I don't understand exactly how that's going to help.
So if you do -- say you locked the window, make some change, invalidates a view. Unlock it, make unlock the window, unlock the window. In between that unlock and lock the window could get an update event and that view would update it.
Now, there are a number of reasons you might want to disable the updates. One, you might want to make sure that those two views get updated at the same time. If it's a perceptual issue you want to make sure that the user sees them being updated at the same time.
Another reason is that for each update message that the window receives there's a transaction going on between the app_server and the window. It has to receive that update. The app_server has to enter into update mode for that window. You send the drawing commands, it closes down. So sending two updates is quite a bit more overhead than sending one update, because it has to go through the port.
A Speaker: So that doesn't reduce the cost of drawing operations in one, it reduces the cost of --
Mr. Hoffman: One viewer would it help? Yes, it could help with a single view if you invalidate a view and then unlock the window and then unlock a view, invalidate again. Say you change a parameter and then you change another parameter. So that view in between could get an update and you might not want that to happen, right, because it might be in an invalid state or at least a state that doesn't make any sense or is not the one you want. So it can also help for a same view.
A Speaker: For a little bit more clarification on that, when it does finally send the update, is that one message, one transaction --
Mr. Hoffman: Yes.
A Speaker: -- that it sends?
Mr. Hoffman: Yes. In the back?
A Speaker: Does BListView currently support two-dimensional lists?
Mr. Hoffman: I don't know. I think the answer is no.
A Speaker: Is there any method or planned method to have drawing modes inside a BeginPicture?
Mr. Hoffman: Are there any plans for it? It's certainly not difficult to do. Why would you want it?
I don't really understand the question. So you want to -- you want to do some drawing, this is like during an update, maybe you want to start a picture before an update, have the application do its update and then end the picture and you've captured its update; is that the idea?
There's ways of doing it. The answer's no, I don't have any plans and I don't think Pierre does and there's other ways of accomplishing that.
A Speaker: I was under the impression that BPicture was on the client port. Let's suppose I've got two thousand numbered circles and about ten thousand lines, all of which I need to call to render. How would you suggest -- now the lines it's begin line rate. How would you suggest acceleration, drawing many multiple circles, request draw picture string?
Mr. Hoffman: How would I suggest accelerating drawing circles within --
A Speaker: I was under the impression you could do this by doing an BeginPicture, EndPicture, then sending everything off to the server at once. If what you're telling me is that the picture on the server side --
Mr. Hoffman: I see what you mean. I see what you mean. When you're doing any drawing commands the commands are cached into packets of some reasonable length on the client side. It doesn't -- we don't do a transaction for each -- each command, so that's taken care of for you to some extent. If you draw two thousand circles, they might get cached into only five app_server transactions, whatever the quantum is for the caching.
A Speaker: Thank you.
Mr. Hoffman Anything else?
A Speaker: I get the impression from playing around with disabling updates that if I resize a view that the update disabling is sort of ignored, because I've noticed when I'm attempting to implement let's say splitter bars, okay, I get a lot of double redrawing and the only way I found to stop it is to actually hide the view, then resize it and then show it.
Mr. Hoffman: You know what, I'm thinking of the code and you're right, it's a bug. It's a bug. Thank you.
A Speaker: That's a yo-yo.
Mr. Hoffman: Okay. I think that's it. (Session concluded.)