Issue 4-32, August 11, 1999

Be Engineering Insights:
I. Making Your BLoopers More Responsive
II. Gamma Correction

By Jean-Baptiste Queru

Some of my fellow engineers tell me that I'm lucky to have so many ideas for my newsletter articles. They couldn't be more wrong. Having to choose between two ideas is much harder (at least for me) than having to find one idea. After whittling a pile of ideas down to just two, I couldn't choose between them, so I'm using both for this article.

You can find the source for them here: ftp://ftp.be.com/pub/samples/graphics/gamma.zip

Part I: Responsive BLoopers

Something that BeOS developers must keep in mind when programming their user interface is that users don't like interfaces to feel slow or sluggish. One key to interface responsiveness is to let the BLooper run freely and process messages. To achieve this goal, you must make sure that your program doesn't spend too much time sitting in the various virtual functions that the BLooper will call in its BHandlers. The sample code for this article shows two techniques that should cover a reasonable number of common cases.

Use Asynchronous Controls

In older versions of BeOS, the only way to track the mouse to dynamically change a control was to repeatedly call GetMouse() in a loop. The BeBook mistakenly suggested doing this in MouseMoved(), which would prevent the BLooper from processing message as long as the mouse button was held down, which could be a very long time. The correct way to do this was to poll in a separate thread, a tricky thing to set up and to tear down without deadlocking.

Fortunately, R4 added an elegant way to solve the problem: the view event mask, which lets you create asynchronous controls. Since this has already been explained in other Newsletter articles, I won't say much about it, but you can look at the sample code to see one of those beasts in action. For more information,

see: Be Engineering Insights: That BeOS is one baaad mother-[Shut your mouth!] ...just talking 'bout BeOS
and
Be Engineering Insights: The Kitchen Sink

Use Other Threads to Do the Actual Work

We're now reaching the heart of the problem. The naive way to implement real time user feedback with asynchronous controls is to redraw the display each time MouseMoved() is called. Change the #if in the sample code to see what happens in that case. This is not what a user wants to see, and it gets worse as the processing time gets longer. A temporary solution might be to eliminate some of the B_MOUSE_MOVED messages by hand in the BMessageQueue before they're dispatched; this only works if the processing stays reasonably short and if the BLooper is a BWindow.

A more general solution is to have another thread do the work for the BLooper, so the BLooper can continue processing messages (e.g., update messages) as they come along. Since setting up and tearing down threads can be painful, and can easily create deadlocks and other race conditions, I've encapsulated all that work in two classes—called SignalSender and SignalCatcher -- that I hope will be reasonably easy to use. Those classes are an example of how easy it is to create your own synchronization classes, and you might even find them useful in their current state.

Part II: Gamma correction.

The sample code is not only interesting because of the programming techniques it uses, but also because of what it does. This small program is actually intended to calibrate a gamma correction system and to use this system to draw anti-aliased lines.

Gamma is a very simple process. The intensity of light created by a CRT is a nonlinear function of its input voltage. Gamma is a parameter that represents this nonlinearity, and gamma correction is the process of compensating for this nonlinearity. Surprisingly, this nonlinearity is the same for all CRTs: the intensity of light produced by the CRT is proportional to the input voltage raised to the power 2.5. You can thus repeat after me: "Gamma is 2.5."

There are, however, many components between the framebuffer (or, more precisely, the DAC) and the CRT's input: various amplifiers, filters, brighness/contrast controls. Overall, those components scale and offset the signal between the DAC and the CRT.

All the math is done in a GammaCorrect class, so you don't have to know exactly how it works if you don't want to.

The patterns displayed by the program are these:

  • In the top-left corner, a median blend of black and white (note that I'm using lines, because some monitors, although expensive, have some limitations that make other patterns yield incorrect results).

  • In the top-right corner, a plain gray that you can select using the control in the bottom of the screen and that you must try to adjust to the brightness of the black and white pattern.

  • In the bottom-left corner, a system of high-frequency ripples that show some circular moire patterns if the gamma correction is not properly calibrated.

  • In the bottom-right corner, some gamma-corrected anti-aliased lines, compared to the standard lines.

Right now, nothing is done by the OS, and everything has to be done by the applications. This will probably change in a future release, but this sample code shows how to use some gamma correction without any OS support.

If you want to know more about how it really works, here's the beginning of an explanation. If we call f the value for a pixel in the framebuffer and l the lightness on the screen for this pixel, we can write that l is proportional to (f+e)^2.5. Since it's more convenient to use values between 0 and 1, we'll renormalize l in that range with to renormalization factors that we'll call a and b, so that l=a*l'+b (l' is the normalized version of l). We can finally write that f=((l'-b)/a)^0.4-e, where f will vary between 0 and 1 when l' varies between 0 and 1.

The sample code will compute a, b, and e from another value m that I call the "mid-light" value; i.e., the gray level that is as light as a median blend of black and white. With ideal monitor settings, m should be approximately 3/4, so that we get a=1, b=0 and e=0.

Part III: Conclusion

I hope this article casts some light on a few useful programming tricks. Please send your feedback to jbq@be.com


Be Engineering Insights: Common ioctls and Error codes for Drivers

By Arve Hjønnevåg

If you look in the file be/drivers/Drivers.h in your system headers, you'll find a list of Be-defined opcodes for ioctls. A driver may choose to implement any of these, but all implemented ioctls need to behave as defined by us. Some ioctls are required for certain types of devices. Let's go through the opcodes defined for Release 4.5.

B_GET_DRIVER_FOR_DEVICE, B_GET_PARTITION_INFO, and B_SET_PARTITION are implemented in devfs. You should not implement any of these.

B_GET_DEVICE_SIZE and B_SET_DEVICE_SIZE are obsolete calls that operate on 32-bit numbers.

B_SET_NONBLOCKING_IO and B_SET_BLOCKING_IO, are used to change a file descriptor to blocking or nonblocking mode. Devices where a read or write operation depends on other events to complete should implement these. The driver also sets the mode on open according to the O_NONBLOCK flag.

B_GET_READ_STATUS and B_GET_WRITE_STATUS are used, in blocking IO mode, to determine if a following read or write will block. A pointer to a bool is passed as the argument, and the driver writes set the value to false if the operation will block, and true if not.

B_GET_GEOMETRY is used to get information about a disk device. All drivers that publish their device under /dev/disk/ should implement this. The argument to this call is a pointer to the following structure:

typedef struct {
    uint32  bytes_per_sector;   /* sector size in bytes */
    uint32  sectors_per_track;  /* # sectors per track */
    uint32  cylinder_count;     /* # cylinders */
    uint32  head_count;         /* # heads */
    uchar   device_type;        /* type */
    bool    removable;          /* non-zero if removable */
    bool    read_only;          /* non-zero if read only */
    bool    write_once;         /* non-zero if write-once */
} device_geometry;

For block devices, bytes per sector should match the block size of the physical device. A file system mounted on your device will not try to read partial blocks. Most devices do not expose accurate information about the relation of cylinders, heads, and sectors, but the number of usable blocks in the device should match sectors per track*cylinder count*head count.

When using this call to determine the size of a device, make sure to use 64-bit precision for the calculation.

device_geometry g;
off_t   devsize;
if(ioctl(devfd, B_GET_GEOMETRY, &g) >= 0) {
    devsize = (off_t)g.bytes_per_sector *
              (off_t)g.sectors_per_track *
              (off_t)g.cylinder_count * (off_t)g.head_count;
}

The device type field indicates the type of the device. The most common values here are B_DISK for hard drives and B_CD for CD-ROMs.

If the media in the device is removable, the removable field should be true. Some devices support media of different size and block size. If there is no media in the drive, report a size and block size of 0.

The read only field is set if the driver knows that the media is not writeable. Some devices may not be able to determine this, so make sure your application deals with write operations failing.

write_once should normally be set to false.

B_GET_BIOS_GEOMETRY, B_GET_BIOS_DRIVE_ID: On the x86 platform, hard drives can be accessed though the bios at boot time and from other operating systems. B_GET_BIOS_GEOMETRY will fill in the same structure as B_GET_GEOMETRY, but sectors per track, cylinder count, and head count need to match the numbers used by the bios. This information is needed to create a partition table that the bios can use. B_GET_BIOS_DRIVE_ID will return the drive number the bios uses for this drive. This information is needed when adding a disk to the boot menu.

B_FORMAT_DEVICE is used to format the media in the device. If your device needs to prepare the media before it can be read and written to, implement B_FORMAT_DEVICE.

B_GET_ICON allows a device to have a custom icon. Currently, our disk drivers have the Be disk and CD-ROM icons built in, but we will provide a simple way to specify standard icons in the future.

B_EJECT_DEVICE and B_LOAD_MEDIA are used to eject or load media.

B GET MEDIA STATUS is used to determine the state of the media in the drive. This is described in detail in my article "Removable Media" <http://www-classic.be.com/aboutbe/benewsletter/volume II/Issue44.html#Insight>.

B_SET_UNINTERRUPTABLE_IO and B_SET_INTERRUPTABLE_IO change how the driver responds to signals. By default a driver should return to user space as soon as possible when it receives SIGINT. A driver can use the B_CAN_INTERRUPT flag when acquiring semaphores and can abort its operation if B_INTERRUPTED is returned. However, when using the file system cache, a read or write operation may carry data that doesn't belong to the current thread. The file system uses B_SET_UNINTERRUPTABLE_IO to tell the driver not to abort any operations. B_SET_INTERRUPTABLE_IO restores the default state. A disk driver that wants to abort IO operations on signals needs to implement these ioctls.

B_FLUSH_DRIVE_CACHE needs to be implemented by disk drivers if the device has a write cache. If the device has a write cache, either a software cache or hardware cache, a call to B_FLUSH_DRIVE_CACHE should not return until all data in the cache has been written to the media.

If an opcode is not supported by your driver, return B_DEV_INVALID_IOCTL. This will cause the ioctl call to return -1 with errno set to B_DEV_INVALID_IOCTL. For all opcodes above you should return B_OK or B_NO_ERROR if the operation was successful. If the operation failed you need to return an error. In the file be/support/Errors.h under your system headers, you'll find error codes for a variety of error conditions. You should try to pick the error code that best describes why the operation failed. Let's look at the most common cases for drivers.

If you don't know why an operation failed, return B_ERROR.

If an operation failed because you could not allocate memory, return B_NO_MEMORY.

If an IO operation fails, return B_IO_ERROR, or if your device has additional information you can return more specific errors such as B_DEV_NO_MEDIA, B_DEV_UNREADABLE, B_DEV_FORMAT_ERROR, B_DEV_TIMEOUT, B_DEV_RECALIBRATE_ERROR, B_DEV_SEEK_ERROR, B_DEV_READ_ERROR, B_DEV_WRITE_ERROR, B_DEV_NOT_READY, and B_READ_ONLY_DEVICE.

If someone tries to write to a read-only device or to write-protected media, return B_READ_ONLY_DEVICE.

For devices with removable media it's also important that B_DEV_NO_MEDIA and B_DEV_MEDIA_CHANGED are returned when appropriate. Once opened, read and write operations should fail with B_DEV_MEDIA_CHANGED if the media changes. See the article "Be Engineering Insights: Removable Media" for more info on how this should be implemented.


Developers' Workshop: What's Going On in Documentation Land?

By Ken McDonald

Recently, the DTS oraA long time ago, in an office far, far away (or at least, on the other side of the building), I wrote a newsletter article about making and using menus in BeOS GUI applications. One of the goals of that article was to see how people responded to a more tutorial style of documentation--something that didn't necessarily go into every last, cotton-pickin' finicky little detail, but that did provide the basic information necessary to use a class or classes in your program, in a compact, easy -to-read form.

We were rather surprised at the number of responses generated by that article, almost all of them positive. Well, it's been a while, and other things came up of course, but now we're back in the tutorial game. In fact, there will hopefully be quite a few more tutorial style documents coming down the pipe in the future.

In light of this (and due to the fact that I didn't have the time or the imagination to do anything else), it seemed apropos to do a sequel to my previous newsletter tutorial. Think of this one as "Looking for Tutorial Feedback II: Nightmare on BView Street". The first writeup in this new tutorial series will be about the BView class, and the first part of this writeup is a short, very high-level introduction located here:

<http://www-classic.be.com/aboutbe/benewsletter/volume_III/resources/articl e.htm l>

I hope the technical content is useful, but the main purpose of presenting this here is to elicit comments from you, the reader. We expect to put quite a bit of effort into this series (hopefully resulting in a complete tutorial for programming the BeOS), and given this, it makes sense to develop a format which will make the information as useful as possible. For example, one of the things I've done in the tutorial is to include a sidebar on the left, giving easy access to the section and subsection headings within the document. Is this a good idea? Can you suggest other features which might be better, or complementary? As the series progresses, I will be locked in to a certain format and approach, in order to maintain consistency--but right now, the sky is the limit. (Well, subject to resource constraints, of course :-) ) So suggest away!

And, of course, please make comments on the writing style and content also. If nothing else, please tell me:

  1. What I'm doing right in the tutorial.

  2. What I'm doing wrong.

  3. What most needs to be covered by this sort of documentation.

You can email directly to me, ken@be.com. (Nope, you don't get my home phone number :-) ). If you could begin the subject line of any feedback with the words "TUTORIAL COMMENTS", it would let me use the cool indexing features of the Be file system to keep responses nicely sorted.


Bit By Bit: Introducing Rephrase

By Stephen Beaulieu

Our application-building exercise begins with Rephrase, a BTextView based text-processing application. Over the next few months, I'll add features in each installment, and explain the BeOS coding philosophy behind them.

You'll find the first version of Rephrase at

<ftp://ftp.be.com/pub/samples/tutorials/rephrase/rephrase0.1d1.zip>

Rephrase 0.1d1
New Features:
Edit text within the window. Drag and drop for open and save. About box.

This limited version of Rephrase is essentially an application wrapped around a standard BTextView. It has a BApplication and a BWindow for the text view to live in. There is an About window, with a basic set of menu items in the main window to bring up the about window, quit the application, and edit the text.

The application's real functionality (limited as it is) comes from the BTextView. It lets you cut, copy, paste, and select the text. It even provides limited file i/o through inherent drag and drop capabilites: select text and drag it to the Tracker to save a text clipping, or drag a file to the view to paste in its contents.

Although it has only 164 lines of code (many of them blank or comments), the application is surprisingly functional. With the addition of resizing, scrollbars and real open and save capabilities, Rephrase could even be useful.

Programming Concepts

BeOS uses a system of messaging to manage most aspects of communication and event handling. BLoopers run an event loop in a thread that receives incoming messages and dispatches them to associated BHandlers to be acted on.

BApplication provides a connection to the Application Server, which enables the messaging system for the app. It also serves as the main looper for interapplication communication and application global services, such as displaying an About window. A global pointer to the BApplication object (be_app), is available throughout the application.

BWindows and BViews are the specialized loopers and handlers for managing the graphical user interface. When visible, a BWindow represents an area on screen. A BWindow maintains a hierarchy of BViews that draw in and handle messages for sections of that area. User interaction generates messages that are delivered to the window, which passes them to the BViews to handle as appropriate.

For example, when you select a menu item in Rephrase, a message is sent to the BMenuItem's target. The menu item's window is the default target. However, the menu item can be told to send its message elsewhere. For instance, when "About Rephrase" is selected from the File menu, a message is sent to the BApplication. All the menu items in the Edit menu send their messages to the BTextView.

Implementation Details

  1. In main(), create the application on the stack, call Run() and return after run completes.

  2. Don't hard-code your interface details. Some views resize themselves to match their contents. Menus, for example, resize to match the size of the characters in their font. After you create and add the menu, determine its size and use that information to place the rest of your views. Change the size of your font in the Menu preferences panel, run Rephrase and see.

  3. Do not delete a BLooper. Send it a B_QUIT_REQUESTED message instead. This calls its QuitRequested() hook function, and if that returns true, will call the Quit() function which tears down the looper.

  4. Windows own their child views and will properly delete them when destroyed. Only delete a view if you have already called RemoveChild() to remove it from its parent.

  5. Invokers own their model messages and will properly delete them when destroyed.

  6. When an app's last window is asked to quit, it should request the app to quit as well. If not, the application may hang around in the Deskbar with no windows.

Next week: Opening a file.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.