琵琶行论坛

 找回密码
 注册

[转载]Understanding MAME Drivers 2

已有 775 次阅读2012-4-25 18:17 |个人分类:emulator


Understanding MAME Drivers
Volume 2: VIDHRDW
by Daniel Boris

Table of Contents

1.0 Introduction

The purpose of this document is to help people understand how a MAME driver
works and to help people who may want to write their own MAME drivers.
A driver is a set of files that are part of the MAME source that define
the operation of a specific arcade game.

For this document I will be describing the video section of the driver for
the game Zarzon. Before reading this document you may want to read over my
Decoding Schematics document which describes the hardware of Zarzon, as well
as the first volume of this document which describes the main driver structures
for Zarzon.  The driver I am going to describe is based on the MAME 0.33
source code for DOS. Some of the things described here may not apply to
other versions of the MAME source.

This volume will describe the functions in the VIDHRDW section of the driver.
The file we will be looking at is /vidhrdw/rockola.c.

2.0 Colors

Before we get into the driver it is a good idea to understand how games like
Zarzon actually generate the graphics on the screen. In it's most basic form
generating the screen is nothing more then determining what color each pixel
on the screen should be.

The graphics for Zarzon start out as a series of characters stored in graphics
ROMS or RAMS. Each pixel in each character is represented by a number from
0 to 3. This number indirectly determines what color each pixel in the character
will be. The important word here is "indirectly". If these numbers became the colors
directly we would have a boring screen display. These color numbers pass through
two more stages before they reach the screen. The first stage is the color map
and the second is the palette.

We'll look at the palette first.  The colors you see on a video display are
actually made up of different 'quantities' of 3 different colors, red, green,
and blue. By varying the intensity of each of these 3 colors you can create
any color you want. The job of the palette is to define the RGB levels for
each color a game can display. The palette can be stored in either ROM (as it
is in Zarzon) or in RAM. Each entry in the palette has a specific number of
bits to represent the intensity of red, green and blue for each color. Zarzon
has 3 bits for red, 3 bits for green and 2 bits for blue. So a palette entry
for a bright red would be 0x07,0x00,0x00. If you wanted white you would have
a palette entry of 0x07,0x07,0x03. In the circuitry of the game these binary
values are converted to analog voltage levels that are used to control the
CRT.

Between the color values from the graphics ROMS/RAMS and the palette there
is another layer called the color map (or color table). The color table is
used to translate the color values into the palette index values. In a very
simple color map (and a pretty pointless one), color value 0 would map to
palette entry 0, color value 1 to palette entry 1, etc. This alone would serve
no purpose so there is one more part to the puzzle.

The video memory for Zarzon (like a lot of other games) stores the number
of the character that is to be drawn at each location on the screen. The video
memory also stores a color value for each location on the screen. This color
value is combined with the color values from the characters and is used as a
lookup into the color map. Here is an example of a simple color map:

0,1,2,3,
0,4,5,6,
0,7,8,9,
0,1,5,8

Each of the numbers in the table is a palette index number. When we draw a
character on the screen we start with the color value from video memory and
this tells us which line to use in the table. The pixel color values from the
graphics ROMS are then used to select one of the 4 values on each line. So
lets say that the first character on the screen has a color value of 2. The
first pixel in the first character has a color value of 1. If you look this up
in the table you would get a value of 7. This value would then be passed to
the palette ROM to get the actual color that will displayed on the screen.

If you notice, each line in the table starts with a 0. The purpose of this
would be that anytime a pixel color value is a 0 it would map to palette
color 0 which in most games is transparent, thus letting graphics overlap
each other.

3.0 vh_convert_color_prom

The first routine we want to look at is satansat_vh_convert_color_prom.
This routine is called when the driver is first started and is used to
create the palette for the game as well as the color table. The beginning of
the routine looks like this:

1 void satansat_vh_convert_color_prom(unsigned char *palette, unsigned short *colortable,const unsigned char *color_prom)
2 {
3        int i;
4        #define TOTAL_COLORS(gfxn) (Machine->gfx[gfxn]->total_colors * Machine->gfx[gfxn]->color_granularity)
5        #define COLOR(gfxn,offs) (colortable[Machine->drv->gfxdecodeinfo[gfxn].color_codes_start + offs])

Line 1 is the start of the routine. All the arguments are pre defined in MAME
so they must appear exactly like this.

Lines 4 and 5 define macros that will be used later to generate the color table.
TOTAL_COLORS(gfxn) will calculate the total number of color table entries
needed for a specific graphics layout (gfxn). COLOR(gfxn,offs) is a shortcut for
referring to a specific colortable entry for a graphics layout.

The next section of code converts the raw color data that was loaded from
the color prom file into the appropriate palette values.

1     for (i = 0;i < Machine->drv->total_colors;i++)
2        {
3                int bit0,bit1,bit2;
4
5
6                /* red component */
7         bit0 = (*color_prom >> 0) & 0x01;
8         bit1 = (*color_prom >> 1) & 0x01;
9         bit2 = (*color_prom >> 2) & 0x01;
10        *(palette++) = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
11                /* green component */
12        bit0 = (*color_prom >> 3) & 0x01;
13        bit1 = (*color_prom >> 4) & 0x01;
14        bit2 = (*color_prom >> 5) & 0x01;
15        *(palette++) = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
16                /* blue component */
17        bit0 = 0;
18        bit1 = (*color_prom >> 6) & 0x01;
19        bit2 = (*color_prom >> 7) & 0x01;
20        *(palette++) = 0x21 * bit0 + 0x47 * bit1 + 0x97 * bit2;
21
22        color_prom++;
23        }

Line 1 starts a loop that will count through each of the colors that the game
uses. The total number of colors was specified in the Machine Driver structure.

Lines 7-10 determine the color value for the red component of the color. The
variable color_prom is a pointer to the color prom data in memory. In line 7
we get a byte from the color_prom and mask off the first bit of the red component.
Line 8 gets the second bit and line 9 gets the third bit. The variable palette
is a pointer to where the palette data should be put in memory. The three
constants in line 10 are based on the resistors that are used in the real
circuit to create the analog voltage level for this color. Line 10 uses
the bit values to calculate the actual red level and stores this in the palette.

Lines 12-15 do the same thing as lines 7-10 but for the green component of the
color.

Lines 17-20 do almost the same thing as lines 7-10, but since the blue component
only has 2 bits instead of 3 we simple set bit0 = 0.

Finally in line 22 we increment the color prom pointer to access the next byte.

The next section of code builds the color tables for this game. The color
mapping for this game is done with a couple logic circuits so the tables
can be calculated mathematically. Some games will use a PROM for the
color mapping in which case the color table can be loaded from the PROM file.

1        backcolor = 0;  /* background color can be changed by the game */
2
3        for (i = 0;i < TOTAL_COLORS(0);i++)
4                COLOR(0,i) = 4 * (i % 4) + (i / 4);
5
6        for (i = 0;i < TOTAL_COLORS(1);i++)
7        {
8                if (i % 4 == 0) COLOR(1,i) = backcolor + 0x10;
9                else COLOR(1,i) = 4 * (i % 4) + (i / 4) + 0x10;
10       }

Line 3 and 4 generate the color table for the foreground graphics plane.
The data that is generated for this is:

0,4,8,12
1,5,9,13
2,6,10,14
3,7,11,15

So if a character is color 0, it's four colors will be 0,4,8,12. If it's color 2
it's four colors will be 2,6,10,14, etc.

Lines 6 - 9 generate the color table for the background graphics plane. The
data that is generated for this is:

16,20,24,28
16,21,25,29
16,22,26,30
16,23,27,31

You will notice that the values start at 16, this is because the background
uses the second half of the palette. You will also notice that the first
number in each line is 16. In Zarzon character color 0 for the background
graphics plane is the background color. The game can change this color so
the driver will modify this number in the colortable whenever the game wants
to change the background color. The variable backcolor (which is setup in
line 1) is used to hold the current background color.


3.1 rockola_characterram_w

This routine is called whenever a write is made to the character shape
memory. This section of memory is used to store the graphics data for the
characters that are draw in the foreground graphics plane. This gets
called from the MemoryWriteAddress structure. 

1  void rockola_characterram_w(int offset,int data)
2  {
3          if (rockola_characterram[offset] != data)
4          {
5                  dirtycharacter[(offset / 8) & 0xff] = 1;
6                  rockola_characterram[offset] = data;
7          }
8  }

The purpose of this routine is to do dirty character handling. Since the
data for these characters is stored in RAM and changes constantly the graphics
data can not be decoded ahead of time like it can if it's stored in ROM. So
while the foreground is being drawn we have to constantly decode the characters.
Once a character has been decoded once, it is a waste of time to decode it
again unless it has changed. The purpose of this routine is to keep track
of when a character's data gets changed so it can be re-decoded.

Line 1 is the declaration of the routine. The parameters are the standard
parameters that are used by any routine called from the MemoryWriteAddress
structure.

In line 3, the variable rockola_characterram was declared in the MemoryWriteAddress
structure so that it points to the character RAM section of memory. offset is
the offset from the beginning of this section of memory to the actual memory
address that was written to. This memory region begins at 0x1000, so if the
cpu writes to location 0x1055 then offset will equal 0x55. This line
compares the data byte that is already in memory with the one being written
to see if it has changed.

The array dirtycharacter in line 5 is used to keep track of whether a character
is "dirty" or "clean". When we redraw the screen we only need to re-decode the
dirty characters. Each character is 8 bytes long so we divide the offset by 8
to get the character number. We then set a flag for that character to indicate
that it is now dirty.

Line 6 writes the new data into memory. Since we have defined our own routine
to handle writes to this memory region, MAME will not do the write for us
so we have to do it manually.

3.2 satansat_b002_w

This routine is another memory write handler. It handles writes to the memory
location 0xB002.

1  void satansat_b002_w(int offset,int data)
2  {
3          /* bit 0 flips screen */
4         if (flipscreen != (data & 0x01))
5        {
6                flipscreen = data & 0x01;
7                memset(dirtybuffer,1,videoram_size);
8        }
9
10        /* bit 1 enables interrupts */
11        /* it controls only IRQs, not NMIs. Here I am affecting both, which */
12        /* is wrong. */
13        interrupt_enable_w(0,data & 0x02);
14
15        /* other bits unused */
16  }

Line 1 is the standard declaration for a memory write routine.

Line 4 checks to see if the flipscreen control bit has changed or not.
flipscreen is used when the machine is in cocktail table mode to invert the
display for the second player. Since the entire screen will be marked dirty
we want to avoid doing this if the flipscreen bit didn't change.

Line 6 stores the new state of the flip screen bit in the variable flipscreen.

Line 7 sets all the tiles as being dirty so they will all be updated next time
the screen is redrawn. The dirty tiles is a little different from the dirty
character we described in the last section. 

Line 13 enables or disables the interrupts for this processor based on the
state of bit 1 in data. 


3.3 satansat_backcolor_w

This routine is called whenever a write is done to location 0xB003. The purpose
of this routine is to change the background color used to draw the background
graphics plane.

1  void satansat_backcolor_w(int offset, int data)
2  {
3        /* bits 0-1 select background color. Other bits unused. */
4        if (backcolor != (data & 3))
5        {
6                int i;
7
8                backcolor = data & 3;
9                for (i = 0;i < 16;i += 4)
10                        Machine->gfx[1]->colortable[i] = Machine->pens[backcolor + 0x10];
11                memset(dirtybuffer,1,videoram_size);
12        }
13  }

Line 4 checks whether the new the background color is actually being changed.
No point in wasting time changing the color when it didn't really change.

Line 8 masks off the lower 3 bits that are being written which are the backcolor.

Line 9 and 10 steps through the colortable and sets the new background color
for each of the 4 character color values.

Line 11 marks all the tiles dirty so that they will all be updated next time
the screen is refreshed.


3.4 satansat_vh_screenrefresh

This routine is the real work horse for the video section of the driver. This
routine is called once per frame to redraw the screen. The first section of
this routine will redraw the background graphics plane.

1  void satansat_vh_screenrefresh(struct osd_bitmap *bitmap,int full_refresh)
2  {
3         int offs;
5         /* for every character in the Video RAM, check if it has been modified */
6        /* since last time and update it accordingly. */
7     for (offs = videoram_size - 1;offs >= 0;offs--)
8        {
9                if (dirtybuffer[offs])
10                {
11                int sx,sy;
12
13                        dirtybuffer[offs] = 0;
14
15                        sx = offs % 32;
16                        sy = offs / 32;
17                        if (flipscreen)
18                        {
19                                sx = 31 - sx;
20                                sy = 27 - sy;
21                        }
22
23                        drawgfx(tmpbitmap,Machine->gfx[1],
24                                        videoram[offs],
25                                        (colorram[offs] & 0x0C) >> 2,
26                                        flipscreen,flipscreen,
27                                        8*sx,8*sy,
28                                        &Machine->drv->visible_area,TRANSPARENCY_NONE,0);
29                }
30    }
31
32        /* copy the temporary bitmap to the screen */
33    copybitmap(bitmap,tmpbitmap,0,0,0,0,&Machine->drv->visible_area,TRANSPARENCY_NONE,0);

Line 1 is the standard declare for the vh_screenrefresh routine.

Line 7 starts a loop that will step through each character in video memory.
videoram_size was automatically setup by MAME based on the the
MemoryWriteAddress structure.

Line 9 is part of MAME's dirty rectangle handling. Dirty rectangle handling
works like this; the first time we draw the screen we draw every character
on the screen and hold this in a temporary buffer as well as drawing it to the
screen. Next we use the dirtybuffer to mark every character as being clean.
When the game writes to video memory the memory handler determines which
character was changed and marks it as dirty in the dirtybuffer. Now the next
time we redraw the screen we only have to redraw the characters that are
dirty.

Line 9 checks to see if the current tile is dirty or not. It only needs to
be draw if it is dirty.

Line 13 marks the current character as clean.

Lines 15 and 16 calculate the x and y coordinate of the current tile on the
screen.

Lines 17 to 21 handle the cocktail flip for the game. This will cause the
screen to be flipped both horizontally and vertically.

Lines 23 to 28 are the call to MAME's drawgfx routine. This routine is the
main graphics drawing routine that most of the games in MAME use to draw
character graphics to the screen. Here is what each parameter does:

tmpbitmap - This is the bitmap you want the graphic drawn to.

Machine->gfx[1] - This is the gfxlayout entry that you want to use to draw
this graphic. This was defined in the GfxLayout structure in /drivers/rockola.c

videoram[offs] - This is the number of the character in the character set you
want to draw.

(colorram[offs] & 0x0C) >> 2 - This is the color number you want to use to
draw the character. This will be used as a lookup into the colortable to get
the actual colors used to draw the character.

flipscreen,flipscreen - These two parameter indicate if the graphic should be
flipped in the X direction and Y direction respectivly. If they are 0 then
the graphic will not be flipped otherwise it will.

8*sx, 8*sy - These parameters specify the X and Y coordinate in pixels where
you want the graphic to be drawn. In this case sx and sy are the x and y
tile coordinates. Since the tiles are 8 pixels by 8 pixels you have to multiply
x and y by 8 to get the pixel coordinates.
                
&Machine->drv->visible_area - This parameter specifies the clipping area for
the bitmap we are drawing onto. Anything beyond this clipping area will not
be drawn. In this case we simply use the visible area from the Machine driver
structure.

TRANSPARENCY_NONE,0 - These parameters specify the transparency for the graphic.
In this case, since we are drawing the background, no transparency is needed.

Line 33 calls a routine called copybitmap() which move the graphics we just
drew to the screen bitmap (which will eventually get moved to the screen).
The parameters for this routine are as follows:

bitmap - The destination bitmap, in this case the screen bitmap

tmpbitmap - The source bitmap, in this case the background we just drew.

0,0 - These two values indicate if the image should be flipped horizontally
or vertically when it is copied.

0,0 - These two values indicate the x and y coordinates in the destination
bitmap to which you want to copy the source bitmap. In this case the source
and destination are the same size so we are copying to 0,0.

&machine->drv->visible_area - this is a pointer to a rectangle structure that
defines the clipping areas of the destination bitmap.

TRASNPARENCY_NONE,0 - this defines the transparency that should be used in the
copy. Since this is the background there is not transparency needed.

The second part of screen_refresh routine will draw the foreground graphics
plane. The code in this section is very similar to that in the last section
so I will only describe the differences.

1   /* draw the frontmost playfield. They are characters, but draw them as sprites */
2    for (offs = videoram_size - 1;offs >= 0;offs--)
3    {
4                int charcode;
5                int sx,sy;
6
7
8                charcode = rockola_videoram2[offs];
9
10                /* decode modified characters */
11                if (dirtycharacter[charcode] != 0)
12                {
13                        decodechar(Machine->gfx[0],charcode,rockola_characterram,
14                                        Machine->drv->gfxdecodeinfo[0].gfxlayout);
15                        dirtycharacter[charcode] = 0;
16                }
17
18                sx = offs % 32;
19                sy = offs / 32;
20                if (flipscreen)
21                {
22                        sx = 31 - sx;
23                        sy = 27 - sy;
24                }
25
26                drawgfx(bitmap,Machine->gfx[0],
27                                charcode,
28                                colorram[offs] & 0x03,
29                               flipscreen,flipscreen,
30                                8*sx,8*sy,
31                                &Machine->drv->visible_area,TRANSPARENCY_PEN,0);
33        }              

The biggest difference in this routine is lines 10-16. When we drew the
background the data came out of ROM so all the graphics where decoded when
that driver was initialized. The foreground graphics data comes from RAM so
we have to decode the characters on the fly. Most of the characters are
going to be drawn to the screen many times before their shape is changed, so
it would be a waste to decode the character every time we are going to use it.
This is where dirtycharacter handling comes in.

We initially start with every character being marked as dirty. The first time
the character is used it is decoded then is marked as being clean so that
it does not have to be decoded again. As you saw earlier the routine
rockola_characterram_w, will mark characters dirty again when their data
is changed

Line 11 checks to see if the current character is dirty. If it isn't then
we can skip the decode.

Lines 13 and 14 call the decodechar routine which decodes the graphics.
This is the same routine that the MAME core uses to decode the ROM based
graphics when the driver is initialized. The parameters are as follows:

Machine->gfx[0] - This is a pointer to a GfxElement structure for the graphics
you are decoding. MAME creates these structures when the driver is initialized.
There is one for each entry in the gfxdecodeinfo[] structure in the driver file.

charcode - Which character in the set should be decoded.

rockola_characterram - A pointer to the location of the source data to be
decoded.

Machine->drv->gfxdecodeinfo[0].gfxlayout - A pointer to the gfxlayout structue
to use in doing the decode.

Line 15 marks the current character as clean since we just decoded it.

The other important difference in this section of the code is in line 31.
The drawgfx routine has the transparency set to TRANSPARENCY_PEN,0. When the
graphics is drawn, any pixel that is color 0 will not be drawn so that it will
not overwrite the background. Essentially the color 0 is transparent so you
can see the background though it.








                         


路过

雷人

握手

鸡蛋

鲜花

评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

小黑屋|手机版|Archiver|琵琶行论坛 ( 赣ICP备12000882号 )

GMT+8, 2024-3-28 19:25 , Processed in 0.026073 second(s), 6 queries , Gzip On, MemCached On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

返回顶部