


                                     L
                                    / \
                                   /   \
                                  /     \
                                 /       \
                                /         \
                               /           \
                              /             \
                             /               \
                            /                 \
                           X---------L---------M
                          / \       / \       / \
                         /   \     /   \     /   \
                        /     \   /     \   /     \
                       /       \ /       \ /       \
                      /         M---------X         \
                     /           \       /           \
                    /             \     /             \
                   /               \   /               \
                  /                 \ /                 \
                 M-------------------L-------------------X



v0.1, March 3rd, 1999, sim@suse.de



Intro:
------

One of the main MLX / ACL API goals was to make the task of driver writing for
particular 3d graphic card as easy as possible. So, if you intend to write a
new driver and consider the genericity rules of current software, this should
be fairly possible.

It is OK if you have to add some driver specific stuff to common structures,
but this is almost never required, because in such case you'd put the driver
specific things into driver specific parts of code.

Interaction with hardware is something very Linux specific at the moment. I
haven't made serious thoughts about possibility of using MLX on other UNIX
platforms (in that case the stuff would be called Mesa-Unix-X or 'MUX'), but I
think such scenario would be possible and not too difficult to achieve, for the
simple reason that MLX doesn't interact with the system directly, through
system calls, but indirectly, through defined functions interface which
performs necessary calls to kernel module. Moreover, the things required from
system are on some Unices already per default available, without need to write
the kernel module. Basicly, to port MLX to other systems, the XFree86 similar
strategy would be used.

The benefit of MLX specific kernel module is that you don't have to be user
root to run 3d application, and that access to system is strictly reduced to
interaction with graphic hardware. Morover, for things like IRQ and contiguos
buffers for fast data transfers, you need kernel module.



Directory structure of the mlx base code
----------------------------------------

mlx/____
        |
        |__3d
        |       * the 3d drivers directory
        |
        |__3d/glint
	|	* the 3DLabs drivers family
	|
        |__3d/mga
        |       * the Matrox drivers family
	|
	|--3d/???
	.
	.
	.	* add here more 3d drivers
	.
	. 
        |
        |__include
        |       * the common include files needed anywhere in mlx
        |
        |__include/ACL
        |       * the home of aclapi.h file
        |
        |__kernel
        |       * kernel module or other system specific stuff
        |
        |__agent/
        |       * the brain of coordination. This piece of code implements all
	|	  common ACL API initialization functions.
        |
        |__lib/
        |       * The library binarys are stored here. The clients which use
        |         the MLX library should know the path to this directory.
        |
        |__rundemo
	|	* testing stuff
        |
        |__x/
        |__x/glint
        |__x/unshared
        |       * This here is X related stuff, if we want to link the driver
	|	  from X server 'as-is'. Currently, we dont use this directory
	|	  because it is: 1) very complicated to maintain 2) not needed
	|	  at the moment
        |

All driver stuff should go into '3d' directory. It is divided into different
driver familys, where each driver family holds final driver subdirectorys. For
example, 3DLabs drivers are stored into 'glint' directory, and drivers for
different 3DLabs chips would be located in, for example, 'pm', 'pm2', 'pm3'
subdirectorys. If there are some files, which are common for some chip family
i.e. for all drivers from particular family, they go into chip family 
directory. Other, single driver specific stuff, goes into driver's own 
directory.

For new driver, perform folowing pre-setup:

* Create a new driver family directory, let's say, 'mga' for Matrox chipsets

* Think out some short name for the particular driver you want do develop (in
further text I'll use <drv>), for example, 'g200'. One other thing to think out
is some shorthand expression (in further text <DRV>), which would be used as
function/macro prefix or sufix. This strategy alows you easy cut and paste
programming, using the code from already written drivers. In our mga driver
example, let's take "G200" as shorthand expression for g200 chipset.

* Now, copy some other driver completely into your new drivers family dir, and
rename it to you driver name. You can also rename all other driver files there.
The driver files are organized in the way which fits to ACL API modular design,
but nobody forbides you to add more files if required, or to extend the current
design.

* Edit makefile, rename functions from old driver using previously defined
shorthand expresion, clean out the code from old driver, which you think you
don't need... At this point, you are actualy free to do with your driver code
what you want and in the way you want.



Getting started: PCI/AGP device interface
-----------------------------------------

First, your graphic hardware should be somehow recognized through kernel
module:

* Edit file 'scanpci_gpm.h' and read comments there carefuly. You should add
PCI device specific data for your cool card, the most important are vendor and
device id. CHIP_CLASS_* and CARD_* are unique identifier which are defined by
you in file 'project_def.h'. Take care the numbers doesn't overlap with
existing ones. Why doing this, read more in GPMod documantation. Anyway, you
have to extend both structures, SupportedDevices[] and SupportedCards[] with
new card data. A 'scanpci -v' command could be useful in gathering some infos.

* Base regions: Which to mmap into kernel space, you decide in SupportedDevices
structure. For example, to mmap base regions 0, 3, and 5, write something like
BASE(0) | BASE(3) | BASE(5) into SupportedDevices. NOTE: This is only for base
regions which you plan to access from within kernel module, that means, either
from some interrupt handler or indirectly through module specific system calls.
Memory map and direct access to regions from within user space is independent
from this field and achieved with module specific ioctl's from GPClient (see
client_gpm.c)

* That's actualy all you need for basic card access. For more complicated stuff
like interrupts and buffers, you should be familar enough with system and
kernel programming to understand the code written for this area. Although, when
I get some more time, I'll also try to write more info about those weird 
things.



Writing driver initial file
---------------------------

There is only one single point, through which MLX driver agent and ACL API
achieve access to the full functionality of particular driver. This is the
structure defined in 'mlx.h': MLXDriverRec, where the fields should be initialy
defined as folows:

	drvidx:
	- put here -1, this field is later initialized.

	card_type:
	- card type, as you have defined it in SupportedCards[] aray for the
	  hardware which driver should supports (CARD_* define)

	drvmsg, drvinfo:
	 - a pointers to some short and somewhat longer driver info

	init:
	 - the pointer to the main init function, which must exist in your
	   driver init file.

	modflags:
	- put here the flags for modules from ACL API you realy implemented
	  (ACLMOD_* defines from aclapi.h)

	features:
	- a bit weird structure, which tells which 3d features driver or card
	  actualy supports. This is mainly used by Mesa ACL Device Driver, to
	  determine if current scene could be rendered in hardware. If you
	  dont't know what to put here, put simply zeros. Later, after you get
	  more familiar with code, you can update it accordingly to the current
	  driver developement level.

I used to name all such driver records with <DRV>_driver. In our example of
g200 driver, I'd call it 'G200_driver'.

The initial driver file, where this record lives, is the minimum required to
compile or start the new driver. Moreover, the minimum on driver fuctions is a
single init function, which pointer lives in the above structure. When you
wrote this minimum, you'd be able to even run some Mesa application with your
new driver, although it wouldn't be doing anything interesting.

You can see in sample init files from other drivers, that there is also code
for init of other common structures: MLXCardCtx and MLXScreenCtx. Those
structures should be filled with data during initialization of the driver
through AclCreateContext(). The folowing things are also expected to be done in
this part of driver code:

	- Memory mapping of required regions into user space
	- Configuration of framebuffer, depth buffer and textures memory
	- Initialization of access to card resources (PCI_COMMAND)
	- Some basic graphic card settings
	- Gathering of all common informations about the hardware
	- Init of video mode / RAMDAC setup

After all hardware get inited, one more function is called:

	<DRV>InitACLAPIModules()

This function performs binding of fresh created and empty ACLContext and driver
functions from different modules. All driver/ACL API modules are stored in
different files. Module functions should be accessed only through the pointers
of ACLContext->Mod structure, therefore, those functions should be defined as
static in driver files.

To summarize, the driver should have minimum on folowing files:

	<drv>.c		- main driver init file
	<drv>_mod.c	- main module (ACLModules),
	<drv>_rgba.c	- color rgba module (ACLColorRGBAMod),
        <drv>_ci.c	- color index module (ACLColorCIMod),
	<drv>_accel.c	- accelerated drawing module (ACLAccelMod),
	<drv>_lb.c	- local buffer module (ACLLBMod),
	<drv>_tex.c	- texturing module (ACLTexMod),

	<drv>_setup.c	- public functions for rendering context or some
			  other, on the fly, hardware setup.



Hardware and driver rendering contexts
--------------------------------------

Although these are the most important aspects to be considered when writting a
new driver, there are, until now, no strict rules how to organize data for fast
context switching and re-loading of existing contexts. It depends on kind of
the pipeline you are using for rendering, which could be either MMIO or
buffered, or combination of the two.

However, some basic strategys could be though taken in account:

A dumb hardware context structure would be as such as currently implemented in
pm2 driver, file pm2.h. You have a simple C structure, where every field holds
the data of corresponding register, and this field _always_ keep 'default'
rendering context value for client. Your context update function have to
'manualy' update the registers of the card on context switch i.e. to read every
particular field from the structure and to send it to hardware. For easyness,
there is usualy a bunch of macros defined, which gives an object oriented
feels-like. (Actualy, C++ would be an ideal language for MLX)

Other, much better strategy, would be to hold data in some buffer-like
structure, which holds data exactly as it would be written to hardware, so that
data could be simply sucked out of buffer. This is excellent if you access
graphic hardware directly through its FIFO or use DMA capability of the card,
but it could be also used for MMIO access, where you have to write to the
registers indirectly.

Anyway, you have to setup this acording to hw specs of your graphic card, and I
think there are not too much differences in the way of accessing the graphic
core between different card vendors.

What about driver rendering context ? This is a set variables which are grouped
into structure, which we call 'driver rendering context'. Those variables
keep important and graphic rendering context related informations, like the
current value for clearing the depth buffer, some secondary register settings,
etc. See file 3d/glint/glintcommon.h and 'GLINTRenderingValues' structure for
more info.



Extending driver agent to recognize your new driver
---------------------------------------------------

So far, so fine. Now we could finaly teach MLX to recognize our driver. This
happens in file 'agent.c' and MLX global structure 'mlx_drivers', which
keeps pointers to all <DRV>_driver records, being previously defined.

When MLX gets initialized through ACL API init functions, it scans the contents
of this structure and access the wished driver over the index number, which is
nothing else as index of driver init structure pointer in 'mlx_drivers'.

I put there macros in the structure, and defined its elements in agent.h file,
because sometimes we'd probably like to use one or two drivers, and not all
drivers implemented in MLX. IMPORTANT: The last member of 'mlx_drivers'
structure should be a NULL pointer.

So, do the folowing:

* Edit agent.h and add new defines MLXDRIVER_PROTO_<DRV> and MLXDRIVER_<DRV>,
analoguous to existing ones

* Use those macros in agent.c, analogous to existing ones.



The context switching strategys with MLX
----------------------------------------

Every rendering thread or process is associated with its own rendering buffer,
pipeline or context, which are accessed through ACLContext pointer on client
side. This ACLContext is bound with particular hardware context, which holds
all data required for fast hw access (MLXCardCtx). If, as usual in windowed
3d applications, multiple 3d processes acces the single 3d graphic card, the
multiple ACLContexts are bound with one single hardware context MLXCardCtx.

Usualy, there is going to be one ACLContext per rendering process. The data
relevant for hardware setup ( MLXCardCtx ) are held in shared memory - this
is obvious, because the associated hardware is shared too. The clients should
synchronize between themselves.

If ACLContexts are associated with rendering threads, the functions which do
access to the hardware MMIO or rendering buffers should be reentrant. For that
reason, static or global variables must be used carefuly in driver functions.



  MLXCardCtx[0] (hwidx)    Driver XXX
 --------------------      ------------------------
 | **************** |      | aclXXXTriangleFlat() |
 | * ACLContext 1 * | ---> | aclXXXTriangleTex()  |       __________________
 | **************** |      | aclXXXInitTex()      |  ==  | 1.hippy card XXX |
 |                  |      | XXXInitMain()        |  ==  |                  |
 | **************** |      | XXXSetLB()           |  ==   ----------||||||--
 | * ACLContext 2 * | ---> | aclXXXBurnMonitor()  |
 | **************** |      | aclXXXMessUpScreen() |
 --------------------      | ...                  |
                           | ...                  |
  MLXCardCtx[1] (hwidx)    | ...                  |       __________________
 --------------------      | ...                  |  ==  | 2.hippy card XXX |
 | **************** |      | ...                  |  ==  |                  |
 | * ACLContext 3 * | ---> | ...                  |  ==   ----------||||||--
 | **************** |      | ...                  |
 --------------------      ------------------------

                           Driver YYY
                           ------------------------
  MLXCardCtx[2] (hwidx)    | aclYYYTriangleFlat() |
 --------------------      | aclYYYTriangleTex()  |
 | **************** |      | aclYYYInitTex()      |       ____________
 | * ACLContext 4 * | ---> | YYYInitMain()        |  ==  | 1.crappy   |
 | **************** |      | YYYSetLB()           |  ==  | card YYY   |
 |                  |      | aclYYYBurnMonitor()  |  ==   -----||||||-
 | **************** |      | aclYYYMessUpScreen() |
 | * ACLContext 5 * | ---> | ...                  |
 | **************** |      | ...                  |
 --------------------      | ...                  |
                           ------------------------


Basicly, writting or accessing graphic hardware could be done through:

   1. Direct MMIO

- If some function requires direct MMIO access to the board (memory mapped IO
regions of graphic hardware), this board must be first locked and MMIO write
access synchronized with other drawing threads or processes. When using MMIO in
a situations where you have more than one ACLContext on the same board, you are
required to switch the graphic contexts through time-expensive functions which
should update the whole graphic registers area for the particular context. In
such case, the performance approvement could be reached through the system
timers which lock the hardware until a reasonable amount of the graphic tags is
sent through MMIO.

    2. Indirect MMIO / InputFIFO

- The rendering client writes into buffers. It is possible to initialize a
graphic daemon with timers which trigger interrupts. On each interrupt the new
transfer of data from other client's buffer is started (daemon empties the
graphic clients rendering buffers, writting their data to MMIO or InputFIFO).
Buffers must not be contiguous, because the transfer is done by the host CPU,
which is hopefully clever enough...

    3. DMA / IRQs

- similar to the above (2), but the transfer is started and done by the graphic
card's own DMA controller, which, after transfer for particular buffer has been
finished, triggers an IRQ. For the low cost cards, which DMA cannot do memory
scather-gather, the drawing buffers must be contiguous.

    4. Few other remarks:

- the concept of "virtual graphic device" and page faulting doesn't make much
sense on PC graphic hardware which doesn't support context switching in
hardware. However, page faulting could be used for some other things, like
automatic new setting of the buffer pointer after it reached the end of buffer.

- For buffered hardware access, we consider in the most of cases that the host
CPU can fill the buffers faster than graphic processor can empty them.



Function prototypes
-------------------

Take a look at the file 'include/mlxgen.h' - it holds all prototypes required
for public driver functions. The prototypes get defined by simply writting the
required macro with <DRV> as parameter, after including the file 'mlxgen.h'.
For example, in main driver init file, say, g200.c, you need a call to the
modules init function. For that purpose, use GENPROTO_DRIVER_INIT_MAIN(G200),
which then generates the required prototypes.



Debugging
---------

A matter of taste. I prefer to use own defined macros 'LOG' and 'MSG', defined
in file 'log_debug.h', which are then easy to remove through simple change of
macro.



Data types
----------

Internaly, MLX uses T* data types, the same ones the ACL API uses. To sync this
whole datatypes confusion and still keep portabilty requirements of the
interface, MLX defines 'include/local_types.h' which is commonly used be
kernel, ACL API and MLX portions of software. This is done through definition
of -D_LOCAL_TYPES_ during compiling.

On the other hand, ACL API and kernel module could be also used out of MLX, in
which case they define their own (same as those in 'local_types.h') T* types.

