Pyro device driver API

Basic description

The Pyro Video Driver API is unique to Pyro, and may at first appear to be a little confusing. This guide tries to explain the basic structure of a video driver and how the appserver handles video drivers and drawing functions.

The basic driver model

Pyro Video Drivers are written in C++ and provide methods required to open a video framebuffer, accelerated drawing functions and video overlay controls.

The DisplayDriver base class provides unacclerated software rendering functions which can draw a line between two points, or Blit a bitmap into the video framebuffer. The VesaDriver class is a fully functional VESA 2.0 display driver, and is the default display driver which Pyro will attempt to use if no accelerated display driver can be found for the installed hardware. Most video drivers generally inherit from the DisplayDriver base class, but some may choose to inherit from the VesaDriver class. The VesaDriver provides additional VESA mode switching functions which may be used for certain hardware E.g. the Mach64 accelerated driver inherits from the VesaDriver in order to use the VESA mode switching functions for some chipsets.

Functionality

Because the DisplayDriver base class provides basic software rendering functions, an unaccelerated or partially accelerated video driver does not have to offer hardware drawing for all functions. The most basic display driver can implement only the required functions to detect and initialise the video hardware and allow the appserver to handle all of the video drawing in software. A more complete display driver can implement hardware accelerated drawing functions by overriding the various drawing methods in the DisplayDriver class. This generally provides much faster video drawing. A complete video driver would also implement the various Video Overlay functions which can be used by the Media Framework to display accelerated video playback.

The basic API

For the purposes of this guide we'll pretend we have some video hardware called "Fire", and assume we are writing a display driver for that hardware.

The most basic video driver for any hardware must provide the following functions and methods.

Fire::Fire( void );
Fire::~Fire( void );
bool Fire::IsInitiated( void ) const;
area_id Fire::Open( void );
int GetFramebufferOffset();
int Fire::GetScreenModeCount( void );
bool Fire::GetScreenModeDesc( int nIndex, os::screen_mode* psMode );
int Fire::SetScreenMode( os::screen_mode sMode );
extern "C" DisplayDriver* init_gfx_driver( void );

The last function in that list is not a C++ method, but instead a C style function. This function is called when the display driver is initialised. Most display drivers simply implement init_gfx_driver(); to create a new instance of their display driver class, and then do the actual hardware detection and initialisation in the class constructor. The constructor and destructor should be fairly obvious. Generally the constructor will probe for the video hardware using the usual pci_* functions. If supported hardware is found then generally the hardware must be initialised, although this is an internal function of the display driver and will differ between different video hardware. What your initialisation code must do however is create and remap an area over the video framebuffer. This area is returned to the appserver later in the initialisation process and is the only way in which the DisplayDriver base class can access the video framebuffer.

Unless your hardware has a functional VESA BIOS and you have inherited from the VesaDriver class, you will have to provide three methods which are used by the appserver to set the correct video mode. GetScreenModeCount(); should simply return the total number of valid screenmodes. GetScreenModeDesc(); returns a structure which contains the display mode information for the requested display mode. Finally, SetScreenMode(); is used to actually set the desired video mode. GetScreenModeCount(); & GetScreenModeDesc(); are generally implemented in a similiar maner in any display driver as they are hardware independent. SetScreenMode(); is obviously extremely hardware dependent, and how you implement this will depend on your hardware.

IsInitiated(); simply returns true if the driver was able to detect and initialise the video hardware, or false otherwise.

GetFramebufferOffset(); should almost always return 0.

The Open(); method is the last peice of the puzzle. All it does it is return the area ID of the previously created framebuffer area. The appserver can then find the video framebuffer base address from this area and use it to access the video framebuffer to perform drawing functions.

Accelerated Drawing

In previous versions of Pyro the video driver had to make sure that the framebuffer content was up to date after every call to an accelerated method. This causes a big performance hit especially if there is a lot of hardware accelerated drawing and the card/driver uses a command buffer. There are two new methods to solve this problem:

void Fire::LockBitmap( SrvBitmap* pcDstBitmap, SrvBitmap* pcSrcBitmap = NULL );
void Fire::UnlockBitmap( SrvBitmap* pcDstBitmap, SrvBitmap* pcSrcBitmap = NULL );

Both are called by the software rendering methods before/after they access any bitmap. A normal video driver will only have to implement the LockBitmap() method. An example:

Fire::LockBitmap( SrvBitmap* pcDstBitmap, SrvBitmap* pcSrcBitmap = NULL )
{
if( ( pcDstBitmap->m_bVideoMem == false && ( pcSrcBitmap == NULL || pcSrcBitmap->m_bVideoMem == false ) ) || m_bEngineDirty == false )
return;
// ... Lock hardware and call the WaitForIdle() method ...
m_bEngineDirty = false;
}

The m_bEngineDirty flag should be set in every hardware accelerated drawing method. The UnlockBitmap() method is only necessary if the hardware does not allow accesses to the framebuffer and accelerated drawing at the same time.

An accelerated video driver will also provide methods which override the DisplayDriver software rendering methods. There implementation is highly hardware dependent, but most drivers implement methods to accelerate Line drawing, rectangular Fills and Bitmap Blits. The methods are:

bool Fire::DrawLine( SrvBitmap* pcBitMap, const IRect& cClipRect, const IPoint& cPnt1, const IPoint& cPnt2, const Color32_s& sColor, int nMode );
bool Fire::FillRect( SrvBitmap* pcBitMap, const IRect& cRect, const Color32_s& sColor, int nMode );
bool Fire::BltBitmap( SrvBitmap* pcDstBitMap, SrvBitmap* pcSrcBitMap, IRect cSrcRect, IRect cDstRect, int nMode, int nAlpha );

All of these methods receive a pointer to a SrvBitmap class. This class is the internal bitmap which is being rendered too. SrvBitmaps can either be in video memory or user memory, depending on wether they are on screen or off screen. Generally, video hardware cannot perform rendering operations on memory which is not in its own video framebuffer, so you must first check to ensure that the bitmap you are rendering to exists in video memory[1]. If not, you should pass the rendering request down to your base class, which will use the software rendering methods in DisplayDriver.2

DrawLine(); and FillRect(); recieve Color information which indicates the color that the line or fill should be drawn in. You may need to convert the RGBA information contained in the Color32_s class to information which can be used by your video hardware, but this is hardware dependent.

All methods recieve the nMode argument, which indicates the drawing mode which should be used to perform the operation. This argument will specify DM_COPY (A stright drawing operation), DM_OVER (An alpha transparent "stamp" operation where the transparency is either "On" or "Off") or DM_BLEND (An alpha blending operation). DM_COPY and DM_BLEND operations are the most common, and you may choose not to support hardware accelerated DM_OVER and DM_BLEND operations. Generally, passing this drawing operations down to the DisplayDriver methods does not noticably slow down rendering[3].

BltBitmap(); has an alpha parameter but the hardware only has to care about it if it supports the DM_BLEND mode.

Offscreen videomemory bitmaps

If your card supports offscreen bitmaps in videomemory then you should implement this. The appserver can use these bitmaps to store the content of a window. In the constructor of the driver you call InitializeMemory() with the offset of the offscreen area and the size. You also have to provide alignment information for the start of every bitmap and the alignment of the bitmap rows. If you have initialized the memory manager then you use the m_nVideoMemOffset member of a SrvBitmap to set the source/destination offset and the m_nBytesPerLine member to set the source/destination pitch. If you support video overlays you should use the AllocateMemory() method of the DisplayDriver class to allocate memory for it using the memory manager.

Video Overlays

If your hardware supports Video Overlays you may wish to support this functionality in your display driver. There are four methods which you must provide in order to support Video Overalys correctly. They are:

bool Fire::CreateVideoOverlay( const os::IPoint& cSize, const os::IRect& cDst, os::color_space eFormat, os::Color32_s sColorKey, area_id *pBuffer );
bool Fire::RecreateVideoOverlay( const os::IPoint& cSize, const os::IRect& cDst, os::color_space eFormat, area_id *pBuffer );
void Fire::DeleteVideoOverlay( area_id *pBuffer );

Unlike the rendering functions, these functions do not have a software implementation in the DisplayDriver class. Your video driver must either support Video Overlays or the user will not be able to use them at all.[4]

CreateVideoOverlay(); and DeleteVideoOverlay(); are self explanitory. RecreateVideoOverlay(); is used to resize a current Video Overlay or to change the current color space of a Video Overlay.

All of these functions will be highly hardware specific and the functionality is complex. You should refer to actual driver implementations of these methods if you wish to understand how they work.[5]

Footnotes

[1]: SrvBitmap contains a public member called m_bVideoMem which is true if the SrvBitmap exists within the video framebuffer.

if( pcBitMap->m_bVideoMem == false ) { return( DisplayDriver::FillRect( pcBitMap, cRect, sColor ) ); }

If you support offscreen videomemory bitmaps then you also have to use the m_nVideoMemOffset and m_nBytesPerRow members!

[3]: The DisplayDriver class now also supports MMX accelerated functions on hardware which supports MMX. This generally provides quite fast software rendering on most systems.

[4]: Users can instead use the slower Bitmap rendering output provided by the Media Framework.

[5]: At the time of writing the Riva, GeForce, Radeon, Mach64, Intel and SiS drivers implement Video Overlays.