gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > Plotting pixels in Mode 0, How to?

#156772 - Polaris - Tue May 13, 2008 9:54 pm

Here is my current code to accomplish this task, it doesn't work. I'm omitting stuff as Vram bank set up, to make it as short as possible.

Code:
#define TOTALCOLS 32
#define PIXELSPERTILE 64
u16 *bg0MapMemory;
u16 *bg0TileData;

void initBackgrounds() {
   bg0MapMemory =(u16*)BG_MAP_RAM(31);
   bg0TileData =(u16*)BG_TILE_RAM(0);

   BACKGROUND.control[0] = BG_32x32 | BG_COLOR_16 | BG_MAP_BASE(31) | BG_TILE_BASE(0) | BG_PRIORITY(0);
      
   BG_PALETTE[1] = RGB15(0,31,0);
   
//This gives each tile an idividual value.
   int row,col;
   int tileIndex = 0;
   for (row=0; row<32; row++) {
      for (col=0; col<32; col++) {
         bg0MapMemory[row*32 + col] = tileIndex;
         tileIndex++;
      }
   }
}
void plotPixels(int screenPosX, int screenPosY, unsigned short int color)
{
   int tileX,tileY,interiorX,interiorY,pixelNumber,myIndex,myPixel;

   tileX = screenPosX/8; // figure out the "column" we're writing to
   tileY = screenPosY/8; // figure out the "row"
   interiorX = screenPosX - tileX*8; // figure our X offset within the tile
   interiorY = screenPosY - tileY*8; // figure our Y offset withing the tile

   // this next bit calculates the pixel "index" as if the tileData
   // were a u4 array
   pixelNumber = (tileY*(TOTALCOLS*PIXELSPERTILE) + tileX*PIXELSPERTILE) + ((interiorY*8) + interiorX);

   // divide by 4 to get the u16 index
   // (remember that this u16 contains 4 pixels worth of data)
   myIndex = pixelNumber/4;

   // remember which of the 4 pixels we're trying to write to
   myPixel = pixelNumber - myIndex*4;

   // pull a copy of the "original" color, since we need to change
   // only one 4-bit section of the thing
   u16 originalColor = bg0TileData[myIndex];

   // do some bit math to mask out our pixel and then add it to
   // our new color to set just that one pixel
   if (myPixel==0) {
      bg0TileData[myIndex] = (originalColor & 0xFFF0) + color;
   } else if (myPixel==1) {
      bg0TileData[myIndex] = (originalColor & 0xFF0F) + color<<4;
   } else if (myPixel==2) {
      bg0TileData[myIndex] = (originalColor & 0xF0FF) + color<<8;
   } else if (myPixel==3) {
      bg0TileData[myIndex] = (originalColor & 0x0FFF) + color<<12;
            }
}
int main() {
    initBackgrounds();

//Everthing bellow is a really really crud way of plotting many pixels :P
   int bla = 0;
   int delay = 100000;

   while(1){
      if(bla < 1024){
         if(delay > 0){
            delay--;
         }
         else{
            delay = 100000;
            plotPixels(bla, 0, RGB15(0,31,0));
            bla++;
         }
      }
   }

    return 0;
}


I found this digging on the forums in the following thread
http://forum.gbadev.org/viewtopic.php?t=7160&highlight=plotting+pixels+mode.
There the original poster said it was incomplete code, but I took it any way to see if I could complete it. Turns out it's a bit tougher than expected.

Anyone familiar with this, probably already knows that the main problem with this approach is saving the actual pixel you want, because writes into VRAM are only 16 bit and pixels are 4 bit, so you end up taking 4 pixels at a time.
It took me a little while, but I realised this particular part is not right, because myPixel always ends up as 0.
Code:

   // divide by 4 to get the u16 index
   // (remember that this u16 contains 4 pixels worth of data)
 myIndex = pixelNumber/4;

   // remember which of the 4 pixels we're trying to write to
   myPixel = pixelNumber - myIndex*4

Any ideas as to what I should do in order to tell into which one of the four pixels I should be writting in?

The other thing I don't yet understand fully is the bit masking part.
Code:
bg0TileData[myIndex] = (originalColor & 0xFFF0) + color;

I do understand that & 0xFFF0 clears the lowers bits, which is exactly what I want.
What I don't get that much is the part where the color is added. Shouln't I be doing something like
Code:
bg0TileData[myIndex] = (originalColor & 0xFFF0);
bg0TileData[myIndex] |= color;


To my understanding that should set the apropiate bits. But my understanding is not very reliable, so some help would be apreciated.

I'll leave it at that for now.

#156774 - TwentySeven - Tue May 13, 2008 10:15 pm

re: the color thing, if color > 0xF the + and | are going to have the same result.

#156780 - Maxxie - Tue May 13, 2008 10:44 pm

Polaris wrote:

Code:

   // divide by 4 to get the u16 index
   // (remember that this u16 contains 4 pixels worth of data)
 myIndex = pixelNumber/4;

   // remember which of the 4 pixels we're trying to write to
   myPixel = pixelNumber - myIndex*4

Any ideas as to what I should do in order to tell into which one of the four pixels I should be writting in?


This should work if it is executed this way, however i am not sure if the /4 and the later use of *4 gets optimized away so that for the later instruction the original pixelNumber value is used rather then (pixelNumber / 4) * 4.

To avoid this, you can set
Code:

myIndex = (pixelNumber >> 2) ; // using all but the lower two bits for addressing the pixel's u16
myPixel = pixelNumber & 3 ; // using the lower two bits to index the pixel within it's u16

#156833 - Polaris - Wed May 14, 2008 3:44 pm

Thanks guys, it now works. Next time I read something does exactly the same as something else, programatically, I might stab myself. The description should always be "Very Similar".

I'm talking about stuff like a pointer being the same as a constant array or even more on topic, shifting bits being the same as multiplying or dividing. I'm sure in 99% of the cases it really is up to the user, but not always.

Anyway back on topic, cause there is still something that's puzzling me.

If you see my code again, this part particularly
Code:
BG_PALETTE[1] = RGB15(0,31,0);

There I set up the second Index as color green and that is fine. What I don't understand is what is going on when I tell a certain pixel to be of a certain color.
Code:
plotPixels(0, 0, RGB15(1,0,0));

If I call my plotPixels fuction with those parameters, the pixel is plotted just fine. Which is a bit strange since not only it is not the maximum value, it's the wrong channel!

The question would be, how do I properly use any given palette when plotting pixels individually?

#156834 - Maxxie - Wed May 14, 2008 3:51 pm

Your plotPixels is taking a palette index (in the range of 0-15), not the RGB value as color

BG_PALETTE[num] = RGB15(r,g,b) ;
plotPixels(x,y,num)

#156835 - Polaris - Wed May 14, 2008 4:06 pm

I guess that's what I get for copying and pasting :P

Thanks!

Edit:

Still at this. Recently I got working some code to plot a circle, it was pretty easy seeing as how there is tons of source code waiting to be used.

Now I'm working on implementing a flood fill algo, and it seems all I have left to do is figure out a way to get the color of a pixel, in order to be able to check if it is already painted or not.

So back on topic, I figured that to do this I would need to make a function that does pretty much the same as the one on my first post, but instead of setting values I would need to return them. That last part I'm not quite getting.

What should I be doing to return those 4-bits representing the color of the pixel? I'm guessing I need to do some bit shifting with the u16 that contains the pixel I want to get, but since I'm very new to bitwise operations I still can't tell for sure what is that I have to do.

#156925 - Polaris - Fri May 16, 2008 6:26 am

Figured this out on my own. If anyone finds it usefull, the following code works to find out the palette index of a pixel, in a tiled background using a 16 color palette.

Code:
u16 getPixelColor(int screenPosX, int screenPosY)
{
   int tileX,tileY,interiorX,interiorY,pixelNumber,myIndex,myPixel;

   tileX = screenPosX/8;
   tileY = screenPosY/8;
   interiorX = screenPosX - tileX*8;
   interiorY = screenPosY - tileY*8;

   pixelNumber = (tileY*(TOTALCOLS*PIXELSPERTILE) + tileX*PIXELSPERTILE) + ((interiorY*8) + interiorX);

   myIndex = pixelNumber>>2; 
   myPixel = pixelNumber & 3;

   u16 pixelColor = bg0TileData[myIndex];

   if (myPixel==0) {
      pixelColor &= 0x000F;
      return pixelColor;
   } else if (myPixel==1) {
      pixelColor &= 0x00F0;
      return pixelColor>>4;
   } else if (myPixel==2) {
      pixelColor &= 0x0F00;
      return pixelColor>>8;
   } else if (myPixel==3) {
      pixelColor &= 0xF000;
      return pixelColor>>12;
   }


It's largely the same as setting a pixel color, only the AND's clear different parts of the u16 and the shifts go the other way.

#157195 - Polaris - Tue May 20, 2008 7:16 am

Can anyone with better math skills than I, tell me how many Tile and Map bases is this technique to plot pixels using.

I was looking into setting up a back buffer in order to eliminate any flickering when doing lots of pixel drawing.

Only when I went to change the background settings to make enough room for the back buffer, did I realize how many different tiles I was using.

32*32 looks like a lot of tiles. I still don't have a firm grasp when it comes setting up Tile and Map bases, but it seems like this is using up more than the regular 1 or 2 that can be found in many examples.

#157230 - Cydrak - Tue May 20, 2008 8:07 pm

1 tile * 4 bits/px
= 8px * 8px * 4 bits/px
= 64px * 4 bits/px
= 256 bits = 32 bytes

giving 1 4-bit tile = 32 bytes.

Multiply by 32*32 and you get 1024 4-bit tiles = 32 KB. This is the max for one layer. Remember the screens are only 32*24 tiles, though, so you can use the remainder for maps, etc.

Meanwhile, 1 "tile base" = 16 KB (512 4-bit tiles). Therefore you'll need to separate your buffers by 2 units. You can easily fit four of them in one of A..D, two buffers in bank E, or one in bank H.

You might find all the bitshifting to be a little slow. If it becomes a problem, think how you can work with the VRAM limitation, rather than against it. (Drawing big solid regions? 32 bits hold 8 pixels, a whole tile row...)

#157241 - Polaris - Tue May 20, 2008 11:53 pm

At last, success! Everything is ready to make a nice particle system with out the need of wasting precious sprites!

Oh and thanks for the clarification Cydrak, things look slightly less shady now :P

#157312 - Cearn - Wed May 21, 2008 10:48 pm

Polaris wrote:
Code:
void plotPixels(int screenPosX, int screenPosY, unsigned short int color)
{
   int tileX,tileY,interiorX,interiorY,pixelNumber,myIndex,myPixel;

   tileX = screenPosX/8; // figure out the "column" we're writing to
   tileY = screenPosY/8; // figure out the "row"
   interiorX = screenPosX - tileX*8; // figure our X offset within the tile
   interiorY = screenPosY - tileY*8; // figure our Y offset withing the tile

   // this next bit calculates the pixel "index" as if the tileData
   // were a u4 array
   pixelNumber = (tileY*(TOTALCOLS*PIXELSPERTILE) + tileX*PIXELSPERTILE) + ((interiorY*8) + interiorX);

   // divide by 4 to get the u16 index
   // (remember that this u16 contains 4 pixels worth of data)
   myIndex = pixelNumber/4;

   // remember which of the 4 pixels we're trying to write to
   myPixel = pixelNumber - myIndex*4;

   // pull a copy of the "original" color, since we need to change
   // only one 4-bit section of the thing
   u16 originalColor = bg0TileData[myIndex];

   // do some bit math to mask out our pixel and then add it to
   // our new color to set just that one pixel
   if (myPixel==0) {
      bg0TileData[myIndex] = (originalColor & 0xFFF0) + color;
   } else if (myPixel==1) {
      bg0TileData[myIndex] = (originalColor & 0xFF0F) + color<<4;
   } else if (myPixel==2) {
      bg0TileData[myIndex] = (originalColor & 0xF0FF) + color<<8;
   } else if (myPixel==3) {
      bg0TileData[myIndex] = (originalColor & 0x0FFF) + color<<12;
            }
}

There are a number of ways to both shorter and speed up this function. First, you have a if/else block where the clauses are identical except for some number, there's usually an arithmetical solution. In this case, what you really need is the shift of the nybble you need to update, which is simply x%8*4 (by the way x-x/a*a is essentially the definition of x%a). Then you follow the normal procedure of inserting a bitfield:
Code:
// Inserting a 4-bit value into data.
u32 shift= x%8*4, mask= 15<<shift;
data = (data &~ mask ) | ((value&mask)<<shift);

As Cydrak mentioned, 8 pixels of 4-bit data fits nicely into a 32-bit word. This means you don't have to figure out which halfword the pixel belongs in. This also makes finding the address you need to access a little easier. In the y-direction, you can simply use y%8 for the internal word; y/8 you'll need to multiply with the number of words in a row of tiles, which is works out to be the width of the surface (#bytes per tilerow = width*bpp/8*8 =width*4 bytes = width words).
The routine then becomes:
Code:

u32 *bg0TileData;

//! Plot a pixel on a 4bpp tiled surface.
void chr4r_plot(unsigned int x, unsigned int y, u16 color)
{
   u32 *dst= &bg0TileData[y/8*SCREEN_WIDTH + y%8 + x/8];
   u32 shift= x%8*4;

   *dst = (*dst &~ 15<<shift) | (color&15)<<shift;
}

And yes, the multiplies, divisions and modulos will be optimized to shifts and ANDs. Mostly anyway. Since for negative numbers division and shifts give different answers, using signed numbers would use extra code. Hence the unsigned types for x and y.

There's also an alternative tile-rendering mode that's actually better. Right now the tiles are ordered in row-major order, i.e., tile 1 is to the right of tile 0. In column-major order tile 1 is under tile 0, meaning that the words of a column of tiles are all adjacent. This eliminates the need for splitting the y coordinate.

Code:

Row major:
   __________________
  |00 01 02 ..    31 |
  |32 33 ..          |
  |64                |
  | :                |
  | _________________|

Colum major:
   __________________
  |00 32 64 ..       |
  |01 33 ..          |
  |02                |
  | :                |
  |__________________|

This will require some changes in the rendering functions and
map initialization:
Code:

u32 *bg0TileData;

//! Plot a pixel on a 4bpp tiled surface, column-major.
void chr4c_plot(unsigned int x, unsigned int y, u16 color)
{
   u32 *dst= &bg0TileData[y+ x/8*SCREEN_HEIGHT];
   u32 shift= x%8*4;

   *dst = (*dst &~ 15<<shift) | (color&15)<<shift;
}

//! Prepare map for column-major tile rendering,
//!     with a base offset for palswapping/offset tile.
void chr4c_prep_map(u16 *map, u32 base)
{
   u32 ix, iy, entry= base;

   // Note the order of the loops!
   for(ix=0; ix<32; ix++)
      for(iy=0; iy<32; iy++)
         map[iy*32+ix]= entry++;
}

I've recently added a few rendering functions to tonclib for this kind of graphics, including drawing pixels, lines, rectangles and a blitter. If you want to take a look, start here.

#157322 - Polaris - Thu May 22, 2008 12:05 am

The method looked kinda clunky but it looked like an ok solution, to atleast see some stuff on screen, while I learned more on bitwise operations.

Thanks to you, I can concentrate in more joyful things. Thanks!