Skip to content
Victor edited this page Mar 30, 2023 · 22 revisions

Video is very simple in the 32x compared to the Genesis VDP. First of all, only a frame buffer is used, so all drawing should be straightforward, and linear. There has always been talk that the 32X has internal hardware for rotating and scaling, but this isn't true, all is done in software. The slave sh2 usually handles all complex video effects. The 32X video can also be appear on top, or on bottom of the Genesis VDP video, depending on 32x priority levels.

CRAM

The 32x can generate a maximum color palette of 32768 colors. There is also a priority bit, which selects priority between which will be drawn in front of the TV, either the Genesis layer, or 32x layer.

The Frame Buffer

The 32x has two memory sets for displaying graphics. When one memory location is used, graphics are displayed from that location, and no graphics are displayed for the other. These two different locations are called frame buffers, and are 128k bytes each. The 32x selects between them by writing to the VDP Status register, and the frame buffer select can only be changed at any time. When one frame buffer is used, then that frame buffer is displayed on the screen, and the other frame buffer is not displayed, but reads/writes can be made from the frame buffer memory space.

The difference between the frame buffer and the overwrite areas:

  • Frame buffer: writing bytes of zero ignored, writing words of zero okay. Address range: 0x24000000 - 0x24020000
  • Overwrite: writing bytes or words of zero ignored (also applies to Direct color mode). Address range: 0x24020000 - 0x24040000

There's also an undocumented mirror for the framebuffer at address range 0x24040000 - 0x24060000 used by the prototype game "Soulstar X" link

Scanline table

The first 256 WORDS in the frame buffer are the line table... well, the first 240 since that's the maximum number of lines the SuperVDP can output (in PAL mode). Each word is an offset for the related line in the frame buffer. It's a WORD offset, not bytes. This means that using the line table for scrolling moves 256-color pixels by two pixels at a time. Because of that, the SuperVDP has a one pixel scroll setting. Note that Sega specifies that lines start at word 256 or higher. Clearly, you cannot start a line at 0 as that's the line table itself. Starting the first line at 256 puts you beyond the line table.

The VDP Modes

  • PAL = 240 lines of video, screen is refreshed 50 times per second
  • NTSC = 224 lines of video, screen is refreshed 60 times per second

Mode 1, 8 BPP (Bits Per Pixel) MODE

8bpp "packed pixel" mode: 256 simultaneous colors on screen; each pixel is an index into CRAM (can use full screen)

In this mode, the CRAM is used to reference the RGB values for the colors used for each pixel. The data in the frame buffer points to the data in the cram to generate color. Each byte in the frame buffer corresponds to one entry in the cram.

Mode 2, 16 BPP (Bits Per Pixel)

16bpp "direct color" mode: 32,768 simultaneous colors on screen; each pixel is the color value

In this mode, the frame buffer Data is used to reference the RGB values for the colors used for each pixel. Each word in the frame buffer corresponds to one pixel. The CRAM is unused in this mode.

Bit 15 is the priority (helps determine if the 32X or Genesis graphics will be on top), bits 14 to 0 are a BGR color value, where each color field is five bits.

Mode 3, RUN LENGTH MODE

16bpp "run length" mode: 256 simultaneous colors on screen; each pixel is both a number of pixels to display and the index of CRAM

This is a weird mode for the 32X, although there are many purposes for it. A pixel in this mode is generated by a CRAM entry reference, and a number to count how many times to display that given pixel, like RLE compression. This is almost like VDP MODE 1 using 8 BPP, except that each data display entry is 2 bytes.

The advantages of using the RLE video mode

If your engine is optimized for flat polygons, it probably would be faster than the other modes since you'd generally be doing a single write as opposed to several writes for each raster line of the polygon.

SuperVDP

The SuperVDP fill data is a single word that is repeatedly stored to the frame buffer. Please note that the fill cannot cross a 256 word boundary, and has a max length of 256 words. This makes it rather limited for use. With a little effort, you can use the fill hardware for solid poly raster line drawing, which is probably what Sega meant for the fill hardware. While the SH2 rasterizes the poly, the fill hardware actually draws the solid, single-color raster line. That's great for games like Virtua Racing. You can use it to clear the frame buffer, but you'll need to loop over filling sections of 256 words to fill the entire buffer. That may be the fastest way to clear the buffer, but whether or not it's the BEST way depends on the game. As mentioned before, you might not need to clear the buffer at all.

Auto Fill and DRAM (VRAM) access

The screen shift control register

Address 20004102H

When LSB is set to 1, the screen is shifted by 1 pixel to the left.

Switching is allowed at any time, but is only valid from the next line.

Shift bit precaution

Updating the palette (CRAM)

You should only update the palette portion of VRAM (aka CRAM) only in the vertical interrupt handler if and only if the VDP access bit is 1.

Writing to the framebuffer

Be aware that writing bytes of zero (0x00) to the framebuffer does NOTHING. Writing words of zero (0x0000) works.

The overwrite region ignores zero-byte writes for both bytes and words. To summarize:

  • writing 0x00 is ignored
  • writing 0x0000 is ignored
  • writing 0x00ff or 0xff00 ignores the first byte and the second byte respectively
static volatile const char *new_palette;

void pri_vbi_handler(void)
{
    mars_vblank_count++;

    if (new_palette)
    {
        int i;
        volatile unsigned short *palette = &MARS_CRAM;

        if ((MARS_SYS_INTMSK & MARS_SH2_ACCESS_VDP) == 0)
		    return;

        for (i = 0; i < 256; i++)
        {
             palette[i] = COLOR(new_palette[0] >> 3, new_palette[1] >> 3, new_palette[2] >> 3);
             new_palette += 3;
        }

        new_palette = NULL;
    }
}

void Hw32xSetPalette(const char *palette)
{
    new_palette = palette;
}