#156007 - pawob - Tue May 06, 2008 8:22 am
I understand that sometimes pasting here a snippet can be like a waste of time, but I have found by myself by trial and error practically all the code I need, the matter is that people voted in my blog that there's a way to:
1.- Scroll strips using registers or Dma copy, does some patient mind explain or put the code to take a look to know the way to do it?
I promise I'll do some really interesting things ?Have you play Metal Slug, or Mario vs Luigi?, yeah I'm creating a library that makes possible to do both of them in a easy way and if I knew a better code to improve that it could enhance my free library.
Thanks everyone
_________________
http://overrider.blogspot.com
#156165 - pawob - Wed May 07, 2008 10:55 am
I have found a bit information about how to do that, the things to do can be:
A possibility:
1.- Change the wait for VBlank function to:
Code: |
irqInit();
irqEnable(IRQ_VBLANK);
....
while(1)
{
...
WaitForVBlank() ;
}
}
...
void WaitForVBlank()
{
#define ScanlineCounter *(volatile u16*)0x4000006 //Counts through the vertical scanlines until we get a blank
while(ScanlineCounter >= 160){
//Here I change scroll register of the bitmap BG if (Scanline< 30) for instance
}
|
B possibility
To do...after every hblank and remove the WaitForVblank and count them, but I think is better the A.
So, do you recommend to do like the A way?
_________________
http://overrider.blogspot.com
#156167 - eKid - Wed May 07, 2008 11:30 am
The 'A' way isn't a very good way... Usually your program logic is still being processed during the rendering period (maybe you'll start waiting for vblank at line 50+ or so). Also, it's not very good to poll the scanline counter like that when waiting for vblank, it uses up more battery power. You should be using interrupts and the swiWaitForVBlank() function.
You can use HDMA to set the scroll register every hblank for you. This way you can write all the values into a small buffer (192 entries, 1 for each line), and tell dma to copy 1 value every hblank.
psuedocode:
Code: |
u16 scroll_buffer[192];
during program:
fill scroll_buffer with values
during vblank:
DMA1CNT = 0;
SCROLL_REGISTER = scroll_buffer[0]; // the first line must be set manually
DMA1SAD = scroll_buffer+1; // start copying from second line
DMA1DAD = &SCROLL_REGISTER;
DMA1CNT = ENABLE | START_HBLANK | SRC_INCREMENT | DEST_FIXED;
|
It's a bit hard to explain.. :)
#156275 - pawob - Thu May 08, 2008 11:53 am
I have found an interesting post:
http://forum.gbadev.org/viewtopic.php?t=300&start=0&postdays=0&postorder=asc&highlight=hdma
Inside, headspin makes a really interesting snippet (I've been reviewing the video.h... and the registers exists in the NDS, so I suppose can be used.
But I would modify the code to a snippet like this:
Code: |
const u16 offset_data[192] =
{
128, 131, 134, 137, 140, 143, 146, 149,
152, 155, 158, 161, 164, 167, 170, 173,
176, 179, 182, 185, 187, 190, 193, 196,
198, 201, 203, 206, 208, 211, 213, 215,
217, 220, 222, 224, 226, 228, 230, 232,
233, 235, 237, 238, 240, 241, 243, 244,
245, 246, 247, 248, 249, 250, 251, 252,
...
};
#define REG_DISPSTAT *(u16*)0x4000004
#define REG_VCOUNT *(vu16*)0x4000006
#define REG_BG0HOFS *(u16*)0x4000010
#define REG_IE *(u16*)0x4000200
#define REG_IF *(u16*)0x4000202
#define REG_IME *(u16*)0x4000208
#define INT_VBLANK 0x0001
#define INT_HBLANK 0x0002
typedef void (*fnptr)(void);
#define REG_INTERRUPT *(fnptr*)(0x03007FFC) //That wasnt in headspin code
void effect()
{
REG_IME=0; //Desactivate interrupt control
REG_IE|=INT_HBLANK; //Flagging up the hblank interrupts
REG_DISPSTAT|=(1<<4); //Necessary to use hblank register
REG_IME=1; //Activate interrupt control
}
void InterruptHandler(void)
{
u16 temp_reg_if=REG_IF;
REG_IME=0; //Disable interrupt control
*((u16*)0x03007ff8) = REG_IF; //We put in BIOS the state of the interrupts activated?
if(temp_reg_if & INT_HBLANK)
{
REG_IF |= INT_HBLANK;
REG_BG3HOFS = (offset_data[REG_VCOUNT]) >> 3; // bit shifting makes a smaller wave
}
REG_IME=1; //Activate interrupt control
}
main()
{
REG_INTERRUPT=(u32)&InterruptHandler;
effect();
for(;;);
}
|
and I have some questions:
1.- Where is defined REG_INTERRUPT in main, it should be
#define REG_INTERRUPT *(fnptr*)(0x03007FFC) for NDS too, is the address of the BIOS interrupts?
2.- *((u16*)0x03007ff8) = REG_IF; //We put in BIOS the state of the interrupts activated?
3.- How can I keep the rest of graphics the swiforVblank?
_________________
http://overrider.blogspot.com
#156276 - eKid - Thu May 08, 2008 12:17 pm
1) REG_INTERRUPT isn't really a register, its a vector that the bios jumps to when an interrupt triggers.
2) This is for the bios IntrWait SWI's (like swiWaitForVBlank), if you don't write the interrupt flags there then your program will hand when it reaches the vblank wait.
3) ?
And um....coding the main interrupt handler in C makes me ill :P
libnds saves you a bit of hassle with a nice interrupt handler.
This is a bit more elegant in my opinion:
Code: |
#include <nds.h>
#include <registers_alt.h> // this wont be needed soon!
const u16 offset_data[160] = // <- error? ds has 192 lines, not 160
{
128, 131, 134, 137, 140, 143, 146, 149,
152, 155, 158, 161, 164, 167, 170, 173,
176, 179, 182, 185, 187, 190, 193, 196,
198, 201, 203, 206, 208, 211, 213, 215,
217, 220, 222, 224, 226, 228, 230, 232,
233, 235, 237, 238, 240, 241, 243, 244,
245, 246, 247, 248, 249, 250, 251, 252,
...
};
void HBlank_Interrupt(void)
{
REG_BG3HOFS = (offset_data[REG_VCOUNT]) >> 3; // bit shifting makes a smaller wave
}
main()
{
irqInit(); // initialize libnds irq handler
irqSet( IRQ_HBLANK, HBlank_Interrupt ); // set hblank interrupt
irqEnable( IRQ_HBLANK ); // enable hblank interrupt
for(;;);
}
|
#156308 - pawob - Thu May 08, 2008 8:51 pm
I have follow your steps and I have my main like:
Code: |
int _val=0;
void HBlank_Interrupt(void)
{
REG_BG2HOFS =_val;
}
int main()
{
REG_POWERCNT = POWER_ALL_2D;
irqInit(); // initialize libnds irq handler
irqSet( IRQ_HBLANK, HBlank_Interrupt ); // set hblank interrupt
irqEnable( IRQ_HBLANK ); // enable hblank interrupt
FullMainVideo();
Levels level;
//BG0 //BG1 //BG2 //BG3
level.Load(tileset0_bmp,tileset1_bmp,park0_bmp,park1_bmp,Map1,Map2,208,16);
level.Show();
while(1)
{
if(++_val>100)
_val=0;
//swiWaitForVBlank();
}
return 0;
}
|
When I run it, the bg2 doesn't scroll any line but theorically it should
Is necessary to activate another register or something special?
_________________
http://overrider.blogspot.com
#156313 - Maxxie - Thu May 08, 2008 9:00 pm
Your val is not volatile, and thus kept in register rather then writing it back to the memory in your loop.
So that your hblank never notice any change in val, as it looks at the memory.
#156387 - pawob - Fri May 09, 2008 8:28 am
In my whole life as developer I used volatile (I don't renember using that), so I have to put sth like:
Code: |
volatile int _val=0;
void HBlank_Interrupt(void)
{
REG_BG2HOFS =_val;
} ...
|
And that's all? I'll try and I'll tell.
P.S. After looking for "volatile" I found out that is to be able to change the value when there are subprocesses in the machine, so... NDS has sth like threads and processes? A bit more and has an OS :)
_________________
http://overrider.blogspot.com
#156389 - Maxxie - Fri May 09, 2008 9:37 am
No there is no threading in the default setup (there are some libs, but most times you dont need them)
But IRQs can happen at any time too, which requires consistency in the memory corresponding to the variable. However non volatile data can be optimized so that read/writes are reduced as it is not expected that the value will be changed or read by "others" like the IRQ.
#156390 - Dwedit - Fri May 09, 2008 9:38 am
No threads or processes, just interrupts. Interrupts make a program sort-of "threaded", but there's no operating system. Usually the stack for interrupts is very tiny.
For better performance, it is usually better to use HBlank DMA instead of hblank IRQs.
With hblank IRQs, the processor gets interrupted, goes to the BIOS interrupt handler, goes to the libnds interrupt handler, then finally gets to your interrupt handler. Lots of overhead. Use HDMA instead, so instead of all that stack pushing and popping, it (possibly) pauses the CPU for the short duration to transfer 1 word. Only downside to HDMA is that it uses up one of the four DMA channels.
To do hblank DMA:
You build an array of scroll values per scanline
Make a vblank interrupt handler
At vblank:
Write the first value to the register (because HDMA starts with the second scanline)
Set up your HDMA transfer. Set the source to be the address of the second item in the array. Set the destination to be the register. Set the word count to 1, width to 16 bits. Make the destination address either fixed or increment/reload. Make the dma happen on HBlank.
sorry, too tired to provide code right now...
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156396 - eKid - Fri May 09, 2008 12:12 pm
Dwedit wrote: |
Only downside to HDMA is that it uses up one of the four DMA channels. |
Not much else to use DMA on. :P
Unlike GBA which had two channels used up for sound...
#156409 - pawob - Fri May 09, 2008 1:45 pm
Thanks Dwedit, I understand with the las method I stop for 192 times the CPU and its a nonsense, If dma works is the best way I suppose if I made only a small code with the bitmaps.
Quote: |
Make the dma happen on HBlank |
I'll look for in the older posts about, thanks again[/quote]
_________________
http://overrider.blogspot.com
#156449 - pawob - Fri May 09, 2008 10:39 pm
Before trying HDMA i want to try hblank. The goal I want is to scroll only a strip of the bg2, so I want to explain myself:
Code: |
volatile int _val=0;
void HBlank_Interrupt(void)
{
BG2_CX =_val<<8;
} |
1.- I dont know why REG_BG2HOFS doesn't work and BG2_CX works, is deprecated? only for gba?
2.- BG2_CX works but of course makes a tangled effect, but the idea is:
Code: |
volatile int _val=0;
void HBlank_Interrupt(void)
{
if(REG_VCOUNT<30)
BG2_CX =_val<<8;
} |
Is REG_VCOUNT unreadable for DS?, is deprecated?
_________________
http://overrider.blogspot.com
#156453 - Dwedit - Sat May 10, 2008 1:09 am
Are you scrolling a bitmap, tilemap, or affine layer? Tilemaps and affine layers scroll differently.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156463 - eKid - Sat May 10, 2008 4:27 am
HOFS/VOFS are for scrolling a tilemap layer.
CX/CY (reference point registers) are for offsetting bitmap layers. They are in fixed point format so 256 = 1 pixel.
Also, be careful with setting those registers during hblank, they might not work as you expect:
GBATEK wrote: |
Internal Reference Point Registers
The above reference points are automatically copied to internal registers during each vblank, specifying the origin for the first scanline. The internal registers are then incremented by dmx and dmy after each scanline.
Caution: Writing to a reference point register by software outside of the Vblank period does immediately copy the new value to the corresponding internal register, that means: in the current frame, the new value specifies the origin of the <current> scanline (instead of the topmost scanline). |
#156657 - pawob - Mon May 12, 2008 8:43 am
I'm scrolling a bitmap bg (when I control this I'll try with the tile bgs). Well the code "works" with CX, the matter is of course is uncontrollable, because I put only to scroll if VCOUNT<100 (for instance), but the result is a scrolling maddness, I'm not sure exactly why, because in theory, in hblank if I set the scroll it should scroll only the next or actual line, am I wrong?.
Well taking care, this is not working as I want, I'll do via HDMA, so if a charming soul :p, places a snnipet in libnds way, I'll try to make shaking, water and else effects.
Thanks everyone
_________________
http://overrider.blogspot.com
#156673 - pawob - Mon May 12, 2008 3:31 pm
I have just found a good example about what I'm trying to do and I'll try to port using only libnds:
http://www.dev-fr.org/demos-techniques/(demo)-hbl-et-scrolling/
So I have to take care of the code of the next functions:
1.- PA_InitVBL();
2.- irqSet(IRQ_HBLANK, HBL_function);
irqEnable(IRQ_HBLANK);
3.- PA_BGScrollX(0, 1, layerscroll);
4.- PA_WaitForVBL();
If those functions are similar to the libnds, it will be a piece of cake, Does anyone knows if any of this functions has something special?
_________________
http://overrider.blogspot.com
#156674 - eKid - Mon May 12, 2008 3:45 pm
1) PA_InitVBL is probably something like irqEnable( IRQ_VBLANK )
2) is already libnds functions
3) not sure which scroll registers it sets
4) probably equivalent to swiWaitForVBlank()
#156707 - Dwedit - Mon May 12, 2008 11:34 pm
You can also use a Vcount interrupt to start and stop DMA. So if you want a wavy scrolling background between scanlines 32 and 100, you can set up a VCOUNT interrupt for scanline 32, copy initial register value and set up hblank DMA, and also set a vcount interrupt for line 100. Then at scanline 100, cancel HDMA, and maybe write a new scrolling value.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156714 - pawob - Tue May 13, 2008 7:47 am
Well, here come the good news, the next code WORKS!! (on emulator and Real hardware):
Code: |
u16 _val=0;
void HBL_function(void){
s16 vcount = REG_VCOUNT; if(vcount > 192) vcount = 0; // Loop
if((vcount >= 0)&&(vcount < 67)) BG2_CX =_val<<8; // Normal
else if(vcount < 78) BG2_CX =_val<<6;
else if(vcount < 86) BG2_CX =_val<<4;
else if(vcount < 145) BG2_CX =_val<<6;
else BG2_CX =_val<<8; // Normal
}
int main()
{
REG_POWERCNT = POWER_ALL_2D;
irqInit(); // initialize libnds irq handler
irqSet(IRQ_VBLANK, 0);
irqEnable(IRQ_VBLANK);
irqSet( IRQ_HBLANK, HBL_function ); // set hblank interrupt
irqEnable( IRQ_HBLANK ); // enable hblank interrupt
FullMainVideo();
Levels level;
//BG0 //BG1 //BG2 //BG3
level.Load(tileset0_bmp,tileset1_bmp,park0_bmp,park1_bmp,Map1,Map2,208,16);
level.Show();
while(1)
{
if(++_val>255)
_val=0;
swiWaitForVBlank();
}
return 0;
}
|
I hope you find interesting, because you can try experiments, but now the bad news. As Dwedit said it consumes CPU a lot, because if I use the emulator DSmume, tell its at 120% more or less, So it's interesting but now I have to find out how to do with hdma because we dont want a one minute game :p
_________________
http://overrider.blogspot.com
#156715 - Dwedit - Tue May 13, 2008 8:06 am
If you're dividing the screen into sections like that, you probably should use Vcount interrupts instead of HDMA.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156716 - pawob - Tue May 13, 2008 8:23 am
Do you think it'll reduce the CPU using the VCOUNT interrupt? is not another interrupt consuming CPU? What I want know is a way to do reducing the consumption. Do you got any interesting snippet?
_________________
http://overrider.blogspot.com
#156717 - Dwedit - Tue May 13, 2008 8:40 am
I think the cost of doing vcount interrupts is probably less than the cost of filling a 384 byte buffer and using HDMA, but I haven't worked out the exact cycle cost of both approaches.
Vcount interrupts let you trigger an interrupt on a specific scanline number, then inside the interrupt handler, you set a new interrupt and scanline number.
Edit: Now that I think about it, HDMA is probably faster in the end. I guess you should just go for HDMA, since you can use any scroll pattern with the same code.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156719 - eKid - Tue May 13, 2008 9:06 am
Using Vcount gets a bit messy because the interrupt triggers at the beginning of the line (while its rendering). Thus you need to add the overhead of enabling the hblank interrupt inside the vcount interrupt. Then change the scroll register in the hblank interrupt, and then finally disable the hblank interrupt. Filling an HDMA buffer would probably be much less work.
EDIT: Or, if you want to be tricky, you can enable a tiny HDMA transfer in vcount interrupt to write the next value of the scroll register. :P
Last edited by eKid on Tue May 13, 2008 9:09 am; edited 1 time in total
#156720 - Dwedit - Tue May 13, 2008 9:08 am
I didn't test this:
Code: |
u16 _val=0;
u16 hdma_scroll_data[192];
void vblank_handler()
{
//fill hdma_scroll_data with your scrolling data
int line;
int limit;
u16 val=_val;
u16 scroll_value;
line=0;
scroll_value=val<<8;
limit=67;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<6;
limit=78;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<4;
limit=86;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<6;
limit=145;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<8;
limit=192;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
//flush cache so that you don't get old data
DC_FlushRange(&hdma_scroll_data[0],192*2);
//copy first line
BG2_CX = hdma_scroll_data[0];
//Do the HDMA, will happen during hblank
DMA0_SRC=(u32) &hdma_scroll_data[1];
DMA0_DEST=(u32) &BG2_CX;
DMA0_CR = 1 | DMA_ENABLE | DMA_START_HBL | DMA_16_BIT | DMA_SRC_INC | DMA_DST_RESET | DMA_REPEAT; //transfer 1 halfword of data
}
|
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."
#156726 - pawob - Tue May 13, 2008 10:29 am
I haven't tried the next code:
Code: |
u16 _val=0;
u16 hdma_scroll_data[192];
int limits[10]; //Define up to 10 strips////Thats just a example, you can use malloc
int speeds[10]; //Define up to 10 speeds
void vblank_handler()
{
//fill hdma_scroll_data with your scrolling data
int line;
int limit; i;
u16 val=_val;
u16 scroll_value;
line=0;
int n_limits=5;
limits[0]=67;limits[1]=78;limits[2]=86;limits[3]=145;limits[4]=192;
speeds[0]=8; speeds[1]=6; speeds[2]=4; speeds[3]=6; speeds[4]=8;
while (line<193) {
if(line<=limits[i])
scroll_value=val<<speeds[i];
else { //if(i<n_limits-1) {
i++; scroll_value=val<<speeds[i]; }
hdma_scroll_data[line]=scroll_value;
line++;
}
//flush cache so that you don't get old data
DC_FlushRange(&hdma_scroll_data[0],192*2);
//copy first line
BG2_CX = hdma_scroll_data[0];
//Do the HDMA, will happen during hblank
DMA0_SRC=(u32) &hdma_scroll_data[1];
DMA0_DEST=(u32) &BG2_CX;
DMA0_CR = 1 | DMA_ENABLE | DMA_START_HBL | DMA_16_BIT | DMA_SRC_INC | DMA_DST_RESET | DMA_REPEAT; //transfer 1 halfword of data
}
|
I'll try both of them to take a look about performance (Since I was a noob I learned the simplest operations are logic ops and the for creates a maddness plenty of code). It's only optimization not rowing. Obviously thanks for your great help Dwedit
_________________
http://overrider.blogspot.com
#156727 - pawob - Tue May 13, 2008 11:22 am
I thinks is better:
Code: |
while (line<193) {
if(line>limits[i])
i++;
hdma_scroll_data[line]=val<<speeds[i];
line++;
}
|
_________________
http://overrider.blogspot.com
#156745 - pawob - Tue May 13, 2008 4:38 pm
I have tested your code Dwedit both in dsemume and real hardware:
Code: |
u16 _val=0;
u16 hdma_scroll_data[192];
void vblank_handler()
{
//fill hdma_scroll_data with your scrolling data
int line;
int limit;
u16 val=_val;
u16 scroll_value;
line=0;
scroll_value=val<<8;
limit=67;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<6;
limit=78;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<4;
limit=86;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=val<<6;
limit=145;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
scroll_value=0<<8;
limit=192;
for (line=line;line<limit;line++) hdma_scroll_data[line]=scroll_value;
//flush cache so that you don't get old data
DC_FlushRange(&hdma_scroll_data[0],192*2);
//copy first line
BG2_CX = hdma_scroll_data[0];
//Do the HDMA, will happen during hblank
DMA0_SRC=(u32) &hdma_scroll_data[1];
DMA0_DEST=(u32) &BG2_CX;
DMA0_CR = 1 | DMA_ENABLE | DMA_START_HBL | DMA_16_BIT | DMA_SRC_INC | DMA_DST_RESET | DMA_REPEAT; //transfer 1 halfword of data
}
int main()
{
REG_POWERCNT = POWER_ALL_2D;
irqInit(); // initialize libnds irq handler
irqSet(IRQ_VBLANK, vblank_handler);
irqEnable(IRQ_VBLANK);
//irqSet( IRQ_HBLANK, HBL_function ); // set hblank interrupt
//irqEnable( IRQ_HBLANK ); // enable hblank interrupt
FullMainVideo();
Levels level;
//BG0 //BG1 //BG2 //BG3
level.Load(tileset0_bmp,tileset1_bmp,park0_bmp,park1_bmp,Map1,Map2,208,16);
level.Show();
while(1)
{
if(++_val>255)
_val=0;
swiWaitForVBlank();
}
return 0;
}
|
The code is like BG2_CX affects to the whole bg2, not by scanlines strips, so might be have to be in another interrupt time?
_________________
http://overrider.blogspot.com
#156928 - pawob - Fri May 16, 2008 8:32 am
At the end I try my snippet in the last version of dsmume, working at 64 fps so I think 192 interrupts per second are not enough for our strong NDS. I have decided to use as good enough, if you are interested, take a look on my blog on the weekend, I'll put some videos to show its real.
Only a question, if you have a link to a web (like oldies dos scene projects) with routines of making scanline effects I'll try to add to my code.
Thanks for the help of everyone, "It's all in the details, Sawyer"
_________________
http://overrider.blogspot.com