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.

Coding > HDMA

#26312 - ProblemBaby - Sun Sep 12, 2004 8:28 pm

Ive never done that before so I dont really know how to do it!
My idea is to use HDMA for my water effect instead of a HBlank Interrupt first off I want to ask if it will be faster?
here is the code for my interrupt:

Code:

void interrupt()
{
   if (REG_VCOUNT < 160)
   {
      REG_BG2HOFS = 8 + (SIN[((REG_VCOUNT + (Counter>>3) - 80) & 0x7) << 5] >> 8);
      REG_BG2VOFS = 8 + (COS[((REG_VCOUNT + (Counter>>3) - 80) & 0xF) << 4] >> 8);

      REG_BG1HOFS = 8 + (SIN[((REG_VCOUNT + (Counter>>3) - 80) & 0x7) << 5] >> 8);
   }
   else if (REG_VCOUNT == 160)
      Counter++;
}


Is it possible to do this with HDMA? And how is it done hints would be great!

#26314 - poslundc - Sun Sep 12, 2004 8:35 pm

ProblemBaby wrote:
My idea is to use HDMA for my water effect instead of a HBlank Interrupt first off I want to ask if it will be faster?


Yes, it will, so long as it is the only raster effect you are doing. If you want to do more than one, you pretty much need an interrupt.

Quote:
Is it possible to do this with HDMA? And how is it done hints would be great!


Precalculate your values for each scanline during VBlank and store them in an array. Load the value for the first scanline during VBlank; point the DMA to the second row and turn it on during VBlank with the correct settings, and watch the magic happen.

Note that the interrupt handler you wrote is about the worst way to do it in the first place. For HBlank stuff, you always want to precalculate and then just load the values in using HDMA or during your interrupt.

Also note: although HDMA will technically be faster, overall speed doesn't matter much on an HBlank interrupt. What matters is whether or not it's fast enough to get the job done before the HBlank period ends. For this reason, I wouldn't switch away from using an interrupt unless you are positive you won't want to do any other raster effects.

Dan.

#26315 - Lord Graga - Sun Sep 12, 2004 8:38 pm

well, with some quick'n'dirty code, you should set up a table like this:
Code:
for(i=0;i<160;i++){
   bg2h[i] = 8 + (SIN[((i + (Counter>>3) - 80) & 0x7) << 5] >> 8);
   bg2v[i] = 8 + (COS[((i + (Counter>>3) - 80) & 0x7) << 5] >> 8);
   bg1h[i] = bg2h[i];
}


Then you could just HDMA this into the memory at the locations it was meant for. Too bad that you need to use 3 DMA channels for it.

#26319 - poslundc - Sun Sep 12, 2004 8:48 pm

Ouch, I didn't notice that you were changing three things in it.

Anyway, would it not be possible to make an array with each entry 16 bytes long (four bytes for each of BG1HOFS, BG1VOFS, BG2HOFS, BG2VOFS) and have the HDMA copy all 16 bytes each scanline?

Dan.

#26320 - ProblemBaby - Sun Sep 12, 2004 8:51 pm

Ok so it maybe it is better to have an interrupt because I ve sound stuff too!
Just two questions:

1.
With this line
Code:

if (REG_VCOUNT < 160)

nothing happens if I use sound stuff.
Ive no idea of why!

2.
So the way I should do this is that I make 2 LUTs
that I just update each HBLANK and dont increment Counter in the interrupt code. Right?
Maybe I can do something like REG_VCOUNT & 0x7/0xF because it just 8 or 16 different cases.

#26321 - DekuTree64 - Sun Sep 12, 2004 9:08 pm

You can pretty much recycle your existing code there to do it with HDMA, except instead of writing to the registers, write to a table with an entry for each line of the screen.

Normally you can't write to several registers at once with HDMA, but since these are all bunched together, you can. BG1VOFS is right in the middle of them though, which means it will get overwritten by the DMA too. To combat this problem, just include it in the table and set all the values for it to the same thing.

The table will need to have entries for BG1HOFS, BG1VOFS, BG2HOFS and BG2VOFS in that order, followed by another set of values in that order for the next line, on to 160 lines. To make it a little more readable, I'll move BG1HOFS up to the top.

Code:
void MakeTable(u16 *regTable, bg1VofsVal)
{
   s32 vCount;

   // Disable the DMA from last frame
   REG_DM0CNT_H = 0;

   // Fill the table for next frame
   for(vCount = 0; vCount < 160; vCount++)
   {
      // 4 regs per line
      regTable[vCount*4 + 0] = 8 + (SIN[((vCount + (Counter>>3) - 80) & 0x7) << 5] >> 8);
      regTable[vCount*4 + 1] = bg1VofsVal;   // Same every time

      regTable[vCount*4 + 2] = 8 + (SIN[((vCount + (Counter>>3) - 80) & 0x7) << 5] >> 8);
      regTable[vCount*4 + 3] = 8 + (COS[((vCount + (Counter>>3) - 80) & 0xF) << 4] >> 8);
   }

   // We just did all 160 lines for this frame, so always increment Counter
   Counter++;

   Dma0(&REG_BG1HOFS, regTable, 4, DMA_ENABLE|DMA_DEST_INC_RELOAD|DMA_MODE_HBL|DMA_REPEAT);
}


That should look just like your HBlank effect. Make sure you call this function in VBlank though, otherwise you could overwrite values in the table that haven't been used yet.

Incase you haven't seen it before, DMA_DEST_INC_RELOAD is bits 5 and 6 set. It means to increment after each hword/word transferred, but to go back to the original dest after the whole DMA is finished. That means it will copy into BG1HOFS, BG1VOFS, BG2HOFS, and BG2VOFS, and then set back to &BG1HOFS (if you just set dest fixed, it will just copy into BG1HOFS 4 times).

Also, for a completely correct effect, you'll want to manually set the regs to the first set of values in the table, and start the DMA from the second set. Because HBlank occurrs at the END of each line, the top line of the screen will show whatever was copied by the HBlank that occurred last frame on line 159. Your IRQ effect had this problem already though, so the DMA one will look exactly the same without the correction, just wanted to let you know incase it ever gets noticable enough to be worth fixing.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#26325 - LOst? - Mon Sep 13, 2004 12:38 am

DekuTree64 wrote:
You can pretty much recycle your existing code there to do it with HDMA, except instead of writing to the registers, write to a table with an entry for each line of the screen.

Normally you can't write to several registers at once with HDMA, but since these are all bunched together, you can. BG1VOFS is right in the middle of them though, which means it will get overwritten by the DMA too. To combat this problem, just include it in the table and set all the values for it to the same thing.

The table will need to have entries for BG1HOFS, BG1VOFS, BG2HOFS and BG2VOFS in that order, followed by another set of values in that order for the next line, on to 160 lines. To make it a little more readable, I'll move BG1HOFS up to the top.

Code:
void MakeTable(u16 *regTable, bg1VofsVal)
{
   s32 vCount;

   // Disable the DMA from last frame
   REG_DM0CNT_H = 0;

   // Fill the table for next frame
   for(vCount = 0; vCount < 160; vCount++)
   {
      // 4 regs per line
      regTable[vCount*4 + 0] = 8 + (SIN[((vCount + (Counter>>3) - 80) & 0x7) << 5] >> 8);
      regTable[vCount*4 + 1] = bg1VofsVal;   // Same every time

      regTable[vCount*4 + 2] = 8 + (SIN[((vCount + (Counter>>3) - 80) & 0x7) << 5] >> 8);
      regTable[vCount*4 + 3] = 8 + (COS[((vCount + (Counter>>3) - 80) & 0xF) << 4] >> 8);
   }

   // We just did all 160 lines for this frame, so always increment Counter
   Counter++;

   Dma0(&REG_BG1HOFS, regTable, 4, DMA_ENABLE|DMA_DEST_INC_RELOAD|DMA_MODE_HBL|DMA_REPEAT);
}


That should look just like your HBlank effect. Make sure you call this function in VBlank though, otherwise you could overwrite values in the table that haven't been used yet.

Incase you haven't seen it before, DMA_DEST_INC_RELOAD is bits 5 and 6 set. It means to increment after each hword/word transferred, but to go back to the original dest after the whole DMA is finished. That means it will copy into BG1HOFS, BG1VOFS, BG2HOFS, and BG2VOFS, and then set back to &BG1HOFS (if you just set dest fixed, it will just copy into BG1HOFS 4 times).

Also, for a completely correct effect, you'll want to manually set the regs to the first set of values in the table, and start the DMA from the second set. Because HBlank occurrs at the END of each line, the top line of the screen will show whatever was copied by the HBlank that occurred last frame on line 159. Your IRQ effect had this problem already though, so the DMA one will look exactly the same without the correction, just wanted to let you know incase it ever gets noticable enough to be worth fixing.



This is what I wanted as a reply in my thread:
http://forum.gbadev.org/viewtopic.php?t=4013

I wanted to do the same effect. Oh well...



EDIT: Thanks DekuTree64! Your code works perfectly in my game!

AND TO HELP OTHERS:
Code:

      /* Background 0 */
      for (i = 0; i < 160; i++)
      {
         VTable [(i * 2) + 0] = (s16) bg0.x_scroll;
         VTable [(i * 2) + 1] = (s16) bg0.y_scroll;
      }

      /* Scanline 0 */
      REG_BG0HOFS = VTable [0];
      REG_BG0VOFS = VTable [1];
      
      /* Scanline 1 to 160 */
      DMA0(&REG_BG0HOFS, &VTable [2], 0x2, DMA_TIMEING_HBLANK | DMA_ENABLE | DMA_REPEATE | DMA_DEST_RELOAD);


VTable is a s16 type with 160 * 2 entries:
s16 VTable [160*2];

And I didn't forget to turn off the HDMA at the beginning of my VBlank routine:
REG_DMA0CNT_H = 0;

This is just to see if it works. There are no effects associated with this code! Only updating REG_BG0HOFS and REG_BG0VOFS each scanline!

EDIT2:
TOO BAD
Code:

      REG_BG0HOFS = VTable [0];
      REG_BG0VOFS = VTable [1];

This didn't work. It's still showing the top scanline from the last frame :(


EDIT3:
Yay it works:
Code:

      /* Scanline 0 */
      DMA0(&REG_BG0HOFS, &VTable [0], 0x2, DMA_ENABLE);

      /* Scanline 1 to 160 */
      DMA0(&REG_BG0HOFS, &VTable [2], 0x2, DMA_TIMEING_HBLANK | DMA_ENABLE | DMA_REPEATE | DMA_DEST_RELOAD);

The first scanline needed to be DMA's with channel 0.