Elysian Shadows

Hardware Palettes on the Dreamcast

[size=150][b][u]Palettes on the Dreamcast[/u][/b][/size]

The Dreamcast supports two paletted texture modes. 4BPP (16 colors, 2 pixels per byte) and 8BPP (256 colors, 1 pixel per byte). It has an on-chip hardware palette with 1024 entries. That's enough for 4 8BPP palettes or 64 4BPP palettes. Keep in mind that they occupy the same space. You can mix them any way you'd like and even have 8BPP and 4BPP palettes overlapping each other.

It's really quite simple to get this running in KOS. The real problem is actually generating a paletted texture. Afaik, there's no tool in KOS that can do that. My very own texconv however, can. 8-)

For this example I'll keep it simple and generate a texture in the code.

[b][u]Palette format[/u][/b]

What you need to do first is select a palette format. You need to do this before you actually set any palette entries as this affects how you encode the colors. This is a global setting, so you can't mix different formats. 

[list][*]PVR_PAL_ARGB1555

[*]PVR_PAL_RGB565

[*]PVR_PAL_ARGB4444

[*]PVR_PAL_ARGB8888[/list]

For this tutorial I'll stick with the ARGB8888 mode as it's easiest to work with (imo). Keep in mind though that if you use this mode with a texture filter other than point filtering (PVR_FILTER_NONE in KOS), the draw performance will be halved.

[code]pvr_set_pal_format( PVR_PAL_ARGB8888 );[/code]

[b][u]Creating a texture[/u][/b]

To avoid having to supply a texture with this tutorial, I thought I'd generate one instead. I'm doing this with an 8BPP texture since it's a lot simpler than having to pack two pixels into each byte (as with 4BPP). What it comes down to is generating an array of bytes the size of width * height where each byte is an index and then filling this with the pattern of your choice. I've gone with a donut-like pattern.

[code]float distance( float x0, float y0, float x1, float y1 )

{

const float dx = x1 - x0;

const float dy = y1 - y0;

return fsqrt( dx * dx + dy * dy );

}

pvr_ptr_t generate_texture( int width, int height )

{

int i, j, mid_x, mid_y;

float max_dist;

uint8* texbuf;

pvr_ptr_t texptr;

// Calculate texture midpoint and greatest distance from the

// midpoint to another point for future usage

mid_x = width / 2;

mid_y = height / 2;

max_dist = distance( 0, 0, mid_x, mid_y );

// Allocate temp storage in RAM to generate texture in

texbuf = malloc( width * height );

// Generate the texture

for ( i=0; i<height; i++ )

for ( j=0; j<width; j++ )

texbuf[ i * width + j ] = fsin( distance( j, i, mid_x, mid_y ) / max_dist * F_PI ) * 256.0f;

// Allocate VRAM for the texture and upload it, twiddling it in the process

texptr = pvr_mem_malloc( width * height );

pvr_txr_load_ex( texbuf, texptr, width, height, PVR_TXRLOAD_8BPP );

// As the texture is now residing in VRAM, we can free the temp storage

// and return the VRAM pointer

free( texbuf );

return texptr;

}[/code]

[b][u]Generating a strip header[/u][/b]

Before you can actually render anything, you need a strip header. If you've ever textured something on the DC before, this'll be familiar to you. The only new thing here is to specify PAL8BPP as the texture format and specify what palette you wanna use using the PVR_TXRFMT_8BPP_PAL(x) macro. There's a similar one for 4BPP textures called PVR_TXRFMT_4BPP_PAL(x). Valid values for 8BPP are 0..3 and for 4BPP it's 0..63.

[code]pvr_ptr_t texptr;

pvr_poly_cxt_t cxt;

pvr_poly_hdr_t hdr;

texptr = generate_texture( 256, 256 );

pvr_poly_cxt_txr( &cxt, PVR_LIST_OP_POLY, PVR_TXRFMT_PAL8BPP | PVR_TXRFMT_8BPP_PAL( 0 ), 256, 256, texptr, PVR_FILTER_BILINEAR );

pvr_poly_compile( &hdr, &cxt );[/code]And now you're ready to render this as you would render any other textured thing.

[b][u]Modifying the palette[/u][/b]

The hardware palette can be modified using the KOS function [i]void pvr_set_pal_entry(uint32 idx, uint32 value);[/i]. Index is in the range 0..1023 and value is the color value. I've included a function to be called each frame which updates the first 8BPP palette. Feel free to play around with this and see the results change.

[code]void animate_palette( uint32 frame )

{

uint32 i, val;

for ( i=0; i<256; i++ )

{

val = ( frame + i ) & 0xFF;

pvr_set_pal_entry( i, 0xFF00003F | ( val << 16 ) | ( val/2 << 8 ) );

}

}[/code]

[b][u]Example[/u][/b]

Here's the full code for this example.

[code]#include <kos.h>

void send_vertex( uint32 flags, float x, float y, float z, float u, float v )

{

pvr_vertex_t vert;

vert.flags = flags;

vert.x = x;

vert.y = y;

vert.z = z;

vert.u = u;

vert.v = v;

vert.argb = 0xFFFFFFFF;

vert.oargb = 0;

pvr_prim( &vert, sizeof(vert) );

}

float distance( float x0, float y0, float x1, float y1 )

{

const float dx = x1 - x0;

const float dy = y1 - y0;

return fsqrt( dx * dx + dy * dy );

}

pvr_ptr_t generate_texture( int width, int height )

{

int i, j, mid_x, mid_y;

float max_dist;

uint8* texbuf;

pvr_ptr_t texptr;

// Calculate texture midpoint and greatest distance from the

// midpoint to another point for future usage

mid_x = width / 2;

mid_y = height / 2;

max_dist = distance( 0, 0, mid_x, mid_y );

// Allocate temp storage in RAM to generate texture in

texbuf = malloc( width * height );

// Generate the texture

for ( i=0; i<height; i++ )

for ( j=0; j<width; j++ )

texbuf[ i * width + j ] = fsin( distance( j, i, mid_x, mid_y ) / max_dist * F_PI ) * 256.0f;

// Allocate VRAM for the texture and upload it, twiddling it in the process

texptr = pvr_mem_malloc( width * height );

pvr_txr_load_ex( texbuf, texptr, width, height, PVR_TXRLOAD_8BPP );

// As the texture is now residing in VRAM, we can free the temp storage

// and return the VRAM pointer

free( texbuf );

return texptr;

}

void animate_palette( uint32 frame )

{

uint32 i, val;

for ( i=0; i<256; i++ )

{

val = ( frame + i ) & 0xFF;

pvr_set_pal_entry( i, 0xFF00003F | ( val << 16 ) | ( val/2 << 8 ) );

}

}

int main( int argc, char** argv )

{

uint32 frame;

pvr_ptr_t texptr;

pvr_poly_cxt_t cxt;

pvr_poly_hdr_t hdr;

// Initialize the PVR

pvr_init_defaults();

pvr_set_pal_format( PVR_PAL_ARGB8888 );

// Generate a strip header

texptr = generate_texture( 256, 256 );

pvr_poly_cxt_txr( &cxt, PVR_LIST_OP_POLY, PVR_TXRFMT_PAL8BPP | PVR_TXRFMT_8BPP_PAL( 0 ), 256, 256, texptr, PVR_FILTER_BILINEAR );

pvr_poly_compile( &hdr, &cxt );

frame = 0;

while ( ++frame )

{

animate_palette( frame );

pvr_wait_ready();

pvr_scene_begin();

pvr_list_begin( PVR_LIST_OP_POLY );

pvr_prim( &hdr, sizeof(hdr) );

send_vertex( PVR_CMD_VERTEX,       0,   0, 1, 0, 0 );

send_vertex( PVR_CMD_VERTEX,     640,   0, 1, 1, 0 );

send_vertex( PVR_CMD_VERTEX,       0, 480, 1, 0, 1 );

send_vertex( PVR_CMD_VERTEX_EOL, 640, 480, 1, 1, 1 );

pvr_list_finish();

pvr_scene_finish();

}

return 0;

}[/code]

-----

Thoughs? Comments?

This is mostly aimed at people who know how to put textured quads on the screen but have no idea of how to to paletted textures. So is this an ok level or do I need to explain something in greater or less detail?

Falco Girgis
Falco Girgis is the founder and lead software architect of the Elysian Shadows project. He was previously employed in the telecom industry before taking a chance on Kickstarter and quitting his job to live the dream. He is currently pursuing his masters in Computer Engineering with a focus on GPU architecture.