#37200 - BlackDragon777 - Tue Mar 08, 2005 9:17 pm
Hi guys! I am new to GBA programming. I am trying to use mode 3. I want to write a star field scroller. I understand that mode 3 doesn't have support for a hardware double buffer. So you have to write your own. I am not sure that I am going about this 100% correct on the GBA but I attempted to do just that. Here is my code.
Code: |
#include <gba2.h>
#include <stdlib.h>
//MULTIBOOT
#define SCREEN_HEIGHT 160
#define SCREEN_WIDTH 240
unsigned short* videoBuffer = (unsigned short*)0x6000000;
void drawPixel(int x, int y, unsigned short color);
unsigned short buffer[240][160];
void Buffer2Vram()
{
for(int j=0; j<160; j++)
{
for(int i=0; i<240; i++)
{
FrontBuffer[(j*240) + i] = buffer[i][j];
}
}
}
int main(void)
{
for(int j=0; j<160; j++)
{
for(int i=0; i<240; i++)
{
buffer[i][j] = 0x000000;
FrontBuffer[(j*240) + i] = 0x000000;
}
}
u8 x,y;
u8 x1, y1;
x1 = 60;
y1 = 40;
SetMode(MODE_3 | BG2_ENABLE);
while(true)
{
for(int j=0; j<160; j++)
{
for(int i=0; i<240; i++)
{
buffer[i][j] = 0x000000;
}
}
//buffer[10][y1]=RGB(255, 0, 0);
//buffer[10][y1+1]=RGB(255, 0, 0);
Buffer2Vram();
}
return 0;
}
|
Buffer2Vram() will copy the contents of the buffer to the vram. The problem is that when I am not drawing any pixels other than clearing the screen with 0x000000, I get 2 random white pixels that are diagnol in their position with each other. When I do plot a pixel other than black, I get 3 of those pixels in random locations. Can anyone give me a hint as to what I am doing wrong? Thanks in advance!
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37203 - DekuTree64 - Tue Mar 08, 2005 9:32 pm
Your back buffer is going into IWRAM, which is only 32KB. You'll need to put it in EWRAM instead. You can do it either by using an attribute (check the FAQ, I don't remember the exact syntax off the top of my head), or the easy, way:
Code: |
unsigned short *buffer = 0x2000000; // start of EWRAM |
You can't make it a 2D array that way though, so you'll have to access pixels like buffer[y*240+x], which which has the nice side effect of being easier to copy to the screen:
Code: |
memcpy(0x6000000, buffer, 240*160*sizeof(unsigned short));
|
Defining it to be the start of EWRAM like this isn't really a good thing for the long term, but as long as you're not using malloc or putting other variables in EWRAM with attributes, it's fine.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku
#37233 - BlackDragon777 - Wed Mar 09, 2005 3:50 am
It is starting to work better now but I have two questions. Number 1, is the double buffer suppose to be slow because this is a sort of slow process. I mean, it is not REALLY slow, but it is still kind of slow. Also, when I copy 0x000000 into my buffer, I get a multi color screen. Is there a problem in how I copy the black color into the EWRAM buffer? Here is my new code.
Code: |
unsigned short* videoBuffer = (unsigned short*)0x6000000;
void drawPixel(int x, int y, unsigned short color);
unsigned short *buffer = (unsigned short int *)0x2000000;
void Buffer2Vram()
{
memcpy((void *)videoBuffer, (void *)buffer, 240*160*sizeof(unsigned short));
}
int main(void)
{
u8 x,y;
u8 x1, y1;
x1 = 60;
y1 = 40;
SetMode(MODE_3 | BG2_ENABLE);
//memcpy(buffer, (void *)0xFFFFFF, sizeof(buffer));
memcpy((void *)videoBuffer, (void *)buffer, 240 * 160 * sizeof(unsigned short));
while(true)
{
//memcpy((void *)buffer, (void *)RGB(0, 0, 0), 240 * 160 * sizeof(unsigned short));
y1+=1;
memcpy((void *)buffer, (void *)RGB(0, 0, 0), 240 * 160 * sizeof(unsigned short));
buffer[y1 * 240 + x1] = RGB(255,150,150);
Buffer2Vram();
}
return 0;
}
|
Thanks in advance!
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37250 - Cearn - Wed Mar 09, 2005 11:10 am
BlackDragon777 wrote: |
Code: | memcpy((void *)buffer, (void *)RGB(0, 0, 0), 240 * 160 * sizeof(unsigned short)); |
|
The second argument of memcpy is supposed to be an address. RGB(0,0,0) is not an address (you can still use it as one of course, but it really isn't). What you want it memset, not memcpy.
Code: |
memset(buffer, RGB(0,0,0), 240*160*sizeof(unsigned short));
|
For filling buffers it's better to use DMA or CpuFastSet though. Not only because memset uses bytes, not u16 (i.e., unsigned short, hoozah for typedef :) ) so you couldn't use a full 16bit color anyway, but also because they're probably a good deal faster. Yes I know they can be considered 'advanced' topics, but if speed is required it might be wise to look into them at some point. Look in the tutorials and GBATek for details, and perhaps this thread (maybe even this one) too.
#37268 - BlackDragon777 - Wed Mar 09, 2005 7:13 pm
When I use memset, it now DOES work. Thank you so much for helping me. However as I said, it is too slow. I need to learn how to use the DMA on the GBA. Well I looked at the posts you asked me to look at. However When I try to clear the video buffer, it doesn't erase what was there. It just causes my video buffer to flicker when I use the dma clear function. Here is my code.
Code: |
void DMAClearScreen(u16 color) {
//ham_VBAText("clearing screen");
REG_DM3SAD = (u32) & color;
REG_DM3DAD = 0x6000000; //Destination Address
(REG_DM3CNT_L) = (160 * 240);
(REG_DM3CNT_H) = 0x9100;
}
void Buffer2Vram()
{
DMAClearScreen(0x000000);
memcpy((void *)videoBuffer, (void *)buffer, 240*160*sizeof(unsigned short));
}
int main(void)
{
u8 x = 30;
u8 y = 30;
unsigned short color = 0;
SetMode(MODE_3 | BG2_ENABLE);
while(true)
{
//memset((void *)buffer, 0x000000, 240 * 160 * sizeof(unsigned short));
buffer[y * 240 + x] = RGB(255, 150, 150);
buffer[(y+1) * 240 + x] = RGB(255, 150, 150);
buffer[y * 240 + (x+1)] = RGB(255, 150, 150);
buffer[(y+1) * 240 + (x+1)] = RGB(255, 150, 150);
Buffer2Vram();
y++;
}
return 0;
}
|
Any more tips? Maybe a tutorial on the DMA for GBA? Thanks in advance!!!
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37313 - FluBBa - Thu Mar 10, 2005 10:52 am
you're still sending the color as a value not a pointer to a value, the DMA works by reading a value from a memory address and writing the same value to another address.
_________________
I probably suck, my not is a programmer.
#37316 - Cearn - Thu Mar 10, 2005 12:57 pm
FluBBa wrote: |
you're still sending the color as a value not a pointer to a value, the DMA works by reading a value from a memory address and writing the same value to another address. |
True, but it's actually quite subtle. When you pass a value to a function it goes directly into a CPU register (r0 in this case) These don't have a real address. I'm not sure what goes on when you try to take the address of a register, but apparently it's not what you want. Try this:
Code: |
void DMAClearScreen(u16 *pclr) {
//ham_VBAText("clearing screen");
REG_DMA3SAD = (u32)pclr;
REG_DMA3DAD = 0x6000000; //Destination Address
(REG_DMA3CNT_L) = (160 * 240);
(REG_DMA3CNT_H) = 0x8100; // not 0x9100, there's a reason I added the second thread in the other post :p
}
void Buffer2Vram()
{
u16 clr= 0xdead; // a nice blue color as a background
DMAClearScreen(&clr);
memcpy((void *)vid_mem, (void *)buffer, 240*160*sizeof(u16));
}
|
You have to define a variable in the calling function and pass its address to DMAClearScreen. This will fill VRAM with a blue color. Kinda. The problem is that right after you've cleared it, you're filling it with the contents of the buffer again, completely negating the clear.
As for the flicker, there are two reasons for that. First of all, you don't have any kind of vsync, so the transfers can happen right in the middle of a VDraw. That's not good. But even then, a memcpy uses about 4.75 cycles/byte. For 76800 that's about 365k cycles; a VBlank takes 83776, you do the math. An EWRAM->VRAM DMA takes 2cycles/byte, so even then it's too long for a full mode3 fill.
Having said that, mode 3 for animation can be done. DekuTree's using it in his (awesome) eternity demo for example and it seems to work just fine there. Not sure it's something you want to try for a first project, though.
#37330 - tepples - Thu Mar 10, 2005 3:33 pm
It's time to learn sprites.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#37347 - BlackDragon777 - Thu Mar 10, 2005 7:01 pm
Hmm, I have tried what has been suggested. I also found information about vblank and set it up. However I am still getting a flickery screen. Is it just because the speed is too slow or am I not copiing something correctly.
Code: |
#define VCOUNT (*(volatile u16*)0x04000006)
#define vsync() while (VCOUNT != 160);
unsigned short * videoBuffer = (unsigned short*)0x6000000;
unsigned short * buffer = (unsigned short *)0x2000000; // EWRAM
void DMAClearScreen(u16 *pclr) {
//ham_VBAText("clearing screen");
REG_DM3SAD = (u32) pclr;
REG_DM3DAD = 0x6000000; //Destination Address
(REG_DMA3CNT_L) = (160 * 240);
(REG_DMA3CNT_H) = 0x8100; // not 0x9100, there's a reason I added the second thread in the other post :p
}
void Buffer2Vram()
{
u16 color = 0x0000;
DMAClearScreen(&color);
memcpy((void *)videoBuffer, (void *)buffer, 240*160*sizeof(unsigned short));
}
int main(void)
{
u8 x = 30;
u8 y = 30;
unsigned short color = 0;
SetMode(MODE_3 | BG2_ENABLE);
while(true)
{
//memset((void *)buffer, 0x000000, 240 * 160 * sizeof(unsigned short));
buffer[y * 240 + x] = RGB(255, 150, 150);
buffer[(y+1) * 240 + x] = RGB(255, 150, 150);
buffer[y * 240 + (x+1)] = RGB(255, 150, 150);
buffer[(y+1) * 240 + (x+1)] = RGB(255, 150, 150);
vsync();
Buffer2Vram();
if(!((*KEYS) & UP))y++;
}
return 0;
}
|
Thanks again for all your help!
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37400 - FluBBa - Fri Mar 11, 2005 11:33 am
Why are you even clearing the screen if you overwrite it directly afterwards?
I suggest you follow Tepples suggestion, the GBA is not a PC.
_________________
I probably suck, my not is a programmer.
#37425 - BlackDragon777 - Fri Mar 11, 2005 5:01 pm
I want to clear the screen because I want to see only ONE pixel moving on the screen. The simple solution to this problem is to just clear the previous pixel and draw the new one. Yes, I understand the GBA is not a pc. However I want to know what I might be doing wrong that causes this flickery screen based off what I have done. Thanks.
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37435 - tepples - Fri Mar 11, 2005 5:50 pm
You should be clearing the back buffer (in EWRAM), not the screen.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#37439 - Miked0801 - Fri Mar 11, 2005 6:13 pm
You want what? Um Ok,
For moving 1 pixel across the screen, use sprites - it's what the hardware was designed for.
To others on speed of clearing, a very simple stm loop I've found rivals the speed of DMA for this type of operation.
Code: |
clear:
orr r0, r0, r0, shl 16
mov r1, r0
mov r2, r0
mov r3, r0
mov r4, r0
mov r5, r0
mov r6, r0
mov r7, r0
mov r8, #0x06000000
mov r9, #(160*240/2)
loop:
stmia [r8]!, r0,r1,r2,r3,r4,r5,r6,r7
subs r9, r9, #1
jne loop
|
Of course clean up and such. Just wanted to throw this out as well.
#37444 - tepples - Fri Mar 11, 2005 6:52 pm
Your "very simple stm loop" is exactly what is in SWI 0x0c, commonly called CpuFastSet.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#37456 - Miked0801 - Fri Mar 11, 2005 7:42 pm
Lol - true :)
#37461 - BlackDragon777 - Fri Mar 11, 2005 7:59 pm
Hmmm.... I think you are right. I never thought about using a sprite. I will try it! Thanks!
_________________
Kindest Regards,
--Brandon Fogerty
Lead Programmer
Game / Software / Web Application Development
brandon@jujikasoft.com
Http://www.Jujikasoft.com
GOD Bless you Always my Friend!!!
#37622 - Cearn - Mon Mar 14, 2005 10:21 am
BlackDragon777 wrote: |
I want to clear the screen because I want to see only ONE pixel moving on the screen. The simple solution to this problem is to just clear the previous pixel and draw the new one. |
But .... why don't you do exactly what you suggest in the second sentence? Erase the drawn pixel and redraw it somewhere else? There's no need to erase the whole screen (which isn't necessary anyway when you redraw all pixels immediately afterwards).
Getting back to the DMA subject for just a minute, take a look at this (using dkArm r8, -O2 optimisation).
Code: |
void foo()
{
u16 clr= 0x7c00;
REG_DMA3SAD= &clr;
REG_DMA3DAD= 0x6000000;
REG_DMA3CNT= 0x81000000 | (160 * 240);
while(1);
}
|
This will fill the entire screen with a blue color. However, if I remove the while statement the whole thing falls apart. With the while in place, the color is put into the stack (where automatic variables usually go, at least on a PC for example); REG_DMA3SAD loads from the stack and everything's peachy. However, when the while is removed the color won't be put on the stack. In fact, there's no reference of the color at all in the generated assembly code. REG_DMA3SAD still reads from the stack, even though the variable it's supposed to read isn't there. This seems to be an optimisation matter, since turning those off gives the right results.
The same thing happens when you pass the color as a parameter to the function. You may remember there is a simple remedy to gung-ho optimisation: volatile. This should work as desired:
Code: |
void ClearScreen(volatile u16 clr)
{
REG_DMA3SAD= &clr;
REG_DMA3DAD= 0x6000000;
REG_DMA3CNT= 0x81000000 | (160 * 240);
}
|
The actual problem has nothing to do with DMA per s?, so I expect it can turn up in other areas as well. Be careful when taking addresses of automatic variables.