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 > Zooming in mode 1 or 2

#23278 - mymateo - Fri Jul 09, 2004 6:10 am

I'm having some trouble. "What's New?" I should change my nick to Zombie 'cause I'm here picking brains just about every week with something new...

I want to use mode 1 to have 2 plain text backgrounds, and one rot/scale background. I plan on adding a spell to my Marle demo, and for that I need a blue circle to shoot out from behind Marle.

I have somewhat of a handle on actually scaling the background, and I can move the background left/right and up/down.

What it does: When I increase the scale/make the background bigger, it moves to the right and down. Or rather, it LOOKS like it does. What it's actually doing (I think) is the 0,0 point stays put and the rest stretches out.

What I want: I want to pick a point at 32,32 (pixels) and have that the center that it stretches out from.

Brendan Sechter has made a rot/scale demo ("sg_rot_demo.zip" in the sources -> GBA -> C/C++ section) that scales in this fashion. I have looked at the source, but I cannot for the life of me figure out how he manages it. I think he does something in this function...

Code:
void swi_bg_affine_set(void *src, void *dest)
{
  asm volatile ("mov r0,%0\nmov r1,%1\nmov r2,#1\nswi 0x0E0000\n" :
  /* No output */ :
  "r" (src), "r" (dest) :
  "r0", "r1", "r2", "memory");
}


... that moves his data around to scale the background and then move it so it looks like it scales out from the center.

But I really don't know, which is why I am asking for help again. Thanks everyone!

#23282 - ampz - Fri Jul 09, 2004 9:49 am

mymateo wrote:
What it does: When I increase the scale/make the background bigger, it moves to the right and down. Or rather, it LOOKS like it does. What it's actually doing (I think) is the 0,0 point stays put and the rest stretches out.

Yes, that's exactly what happens.
If you want it to center on any other point (like 32,32) you have to compensate by moving the background.

I think this should keep it centered at (32,32).
x=y=32-32*s where s is the scale factor and x,y are the background coordinates.

#23283 - mymateo - Fri Jul 09, 2004 10:41 am

I tried that, but the same thing happens. The upper left corner stays exactly where it is, and the background just stretches out. This is what the code looks like, with your suggestion:

Code:
BG2_ZOOM = 1 + ((Marlex + Marley) / 40);
REG_BG2X = REG_BG2Y = 32-32*BG2_ZOOM;
REG_BG2PA = BG2_ZOOM << 8;
REG_BG2PB = 0;
REG_BG2PC = 0;
REG_BG2PD = BG2_ZOOM << 8;


When my sprite moves around, the zoom changes (I did this so I don't have to recompile the source every time I wanted to test a different zoom factor). Full size (BG2_ZOOM = 1) when the sprite is in the upper left, and small (BG2_ZOOM = 9) when it's in the bottom right. The background (a circle) stays in the upper-left.

So no offense (because I don't think you knew what I wanted), but this isn't going to work.

#23287 - Cearn - Fri Jul 09, 2004 1:46 pm

Always be very very careful when you're dealing with fixed point numbers. For one thing REG_BG2X and REG_BG2Y are both .8 fixed numbers; using a pure 32 doesn't move a pixel. Secondly, are BG2_ZOOM and marlex and marley fixed point or not? If they're pure numbers, the calculation of BG2_ZOOM is likely to be wrong.
Lastly, all the background offsets and affine parameters are write only! Doing anything like
Code:

REG_BG2X = REG_BG2Y = foo;
REG_BG2X += bar;

will probably give unexpected results.

#23288 - sgeos - Fri Jul 09, 2004 1:46 pm

First, find a copy of the GBATEK. It covers a lot of nifty stuff for the GBA.

I am calling a BIOS function, SWI 14 in particular. BIOS functions can only be called using ASM, so that confusing function uses gcc's asm feature. I suspect that you are more interested in how to use the function than you are in how it works.

There are two structs:
Code:
typedef struct tbgaffinesource
{
  s32 x;   //Original data's center X coordinate (8bit frac portion)
  s32 y;   //Original data's center Y coordinate (8bit frac portion)
  s16 tx;  //Display's center X coordinate
  s16 ty;  //Display's center Y coordinate
  s16 sx;  //Scaling ratio in X direction (8bit fractional portion)
  s16 sy;  //Scaling ratio in Y direction (8bit fractional portion)
  u16 r;   //Angle of rotation (8bit fract portion) Effect Range 0-FFFF
} bgaffinesource;

typedef struct tbgaffinedest
{
  s16 pa;  //Difference in X coordinate along same line
  s16 pb;  //Difference in X coordinate along next line
  s16 pc;  //Difference in Y coordinate along same line
  s16 pd;  //Difference in Y coordinate along next line
  s32 x;   //Start X coordinate
  s32 y;   //Start Y coordinate
} bgaffinedest;

These are more or less directly from the GBATEK's section on SWI 14 (0Eh) - BgAffineSet.

Declare your source and destination structs
Code:
bgaffinesource src_bga;
bgaffinedest   dest_bga;

Write all of your source info to the source struct:
Code:
src_bga.x  =    64 * 256;
src_bga.y  =    64 * 256;
src_bga.tx =   120;
src_bga.ty =    80;
src_bga.sx = 0x100;
src_bga.sy = 0x100;

Humans tend to think more along the lines of the source struct representation (bgaffinesource), but the GBA can't deal with that format. The GBA's bios will take the info in the source struct and use that to calculate values the GBA can deal with. These are put in the destination struct (bgaffinedest).

Pass the source and destination structs to swi_bg_affine_set():
Code:
swi_bg_affine_set(&src_bga, &dest_bga);

Next, write the values from your destination struct to your BG rot/scale/control registers.
Code:
// !!!!!!!!!!!
// ! Warning !  Bad code - do not program like this!
// !!!!!!!!!!!

*(unsigned long *) 0x04000028 = dest_bga.x;  // BG2X
*(unsigned long *) 0x0400002C = dest_bga.y;  // BG2Y

*(unsigned short *)0x04000020 = dest_bga.pa;  // BG2 Rotation/Scaling Parameter A (alias dx)
*(unsigned short *)0x04000022 = dest_bga.pb;  // BG2 Rotation/Scaling Parameter B (alias dmx)
*(unsigned short *)0x04000024 = dest_bga.pc;  // BG2 Rotation/Scaling Parameter C (alias dy)
*(unsigned short *)0x04000026 = dest_bga.pd;  // BG2 Rotation/Scaling Parameter D (alias dmy)

-Brendan

PS How easy/hard to read did you find the code in that demo?

#23289 - poslundc - Fri Jul 09, 2004 2:13 pm

Note that the BIOS routine is probably not the preferred option for practical game design, since it is much faster to do your own calculations as described earlier in the thread.

Dan.

#23294 - sgeos - Fri Jul 09, 2004 3:01 pm

poslundc wrote:
Note that the BIOS routine is probably not the preferred option for practical game design, since it is much faster to do your own calculations as described earlier in the thread.

That depends on how you view practical. The BIOS routine will burn more cycles, but if your bottleneck is programmer time and the BIOS function works, it is the fastest and most practical way to go.

-Brendan

#23312 - mymateo - Fri Jul 09, 2004 7:30 pm

Quote:
Secondly, are BG2_ZOOM and marlex and marley fixed point or not? If they're pure numbers, the calculation of BG2_ZOOM is likely to be wrong.


BG2_ZOOM, Marlex and Marley are all u16 numbers. I just << 8 when I need to put them into memory.

Quote:
Lastly, all the background offsets and affine parameters are write only! Doing anything like
Code:
Code:

REG_BG2X = REG_BG2Y = foo;
REG_BG2X += bar;


will probably give unexpected results.


Then it's good I'm not. I store everything I will need to write to the background registers in variables.

sgeos, I'm particularly interested in picking your brain. In your scale/rot demo with the red and blue faces, you manage to get it to zoom from a specific point, yet I cannot find anything in the code that would offset the layer's X and Y co-ords compared to the zoom. The only thing I see that might make any calculations is that funky BIOS call which I sill don't understand, especially since when I try to compile it I get an error that 917504 is too big of a number. (It doesn't like the 0x0E0000 in there I guess....)

Without using ancient egyptian cryptic writings (BIOS stuff written in assembly - whew), could you give me a breakdown of the math that happens?

#23313 - mymateo - Fri Jul 09, 2004 7:45 pm

Hold the phone...

sgeos, I gave implementing your code into mine one more time (out of sheer frustration of not being able to make any progress), and I think I may have it...

Sorry to bother everyone....

Still, I wouldn't mind knowing how that BIOS thingy works.

#23324 - sgeos - Fri Jul 09, 2004 10:16 pm

mymateo wrote:
Still, I wouldn't mind knowing how that BIOS thingy works.

The GBA BIOS contains some routines that do nifty things. Each routine has a number. To execute a given routine, you need to use make a SWI call using assembly code. These routines are more or less just functions. I wouldn't worry about it unless you learn ASM or unless you feel using a particular BIOS function would be really nifty.

How does the BIOS function do the job? I have never dumped or looked at the BIOS code. I have no clue. If anyone knows how SWI 0x0E actually calculates the rotation and scaling parameters I'd love to hear about it.

-Brendan

#23330 - Cearn - Sat Jul 10, 2004 12:02 am

mymateo wrote:
I tried that, but the same thing happens. The upper left corner stays exactly where it is, and the background just stretches out. This is what the code looks like, with your suggestion:

Code:
BG2_ZOOM = 1 + ((Marlex + Marley) / 40);
REG_BG2X = REG_BG2Y = 32-32*BG2_ZOOM;
REG_BG2PA = BG2_ZOOM << 8;
REG_BG2PB = 0;
REG_BG2PC = 0;
REG_BG2PD = BG2_ZOOM << 8;


When my sprite moves around, the zoom changes (I did this so I don't have to recompile the source every time I wanted to test a different zoom factor). Full size (BG2_ZOOM = 1) when the sprite is in the upper left, and small (BG2_ZOOM = 9) when it's in the bottom right. The background (a circle) stays in the upper-left.

Sorry, I should have paid better attention to this post. If BG2_ZOOM is in the range [1,9], then the coordinates you have for REG_BG2X and REG_BG2Y lie between [-256, 0]. Since these are .8 fixed point numbers, that means a one pixel offset at best. And I guess that's not what you're after. Try shifting the whole thing.
Also, since you're using integers for BG2_ZOOM, you will have jumps between its nine values, rather than a smooth scaling.

mymateo wrote:
Still, I wouldn't mind knowing how that BIOS thingy works.


While I'm not exactly sure what the BIOS routine does, it's likely to be something like this (sorry, I'm using my own names here):
Code:

typedef struct tagAFFSRC_EX
{
  s32 px, py;    // map origin (.8 fixed)
  s16 qx, qy;    // screen origin (pure integer (?) )
  s16 sx, sy;     // x and y scales (.8 fixed)
  u16 alpha;
} AFFSRC_EX;

typedef struct BGAFF_EX
{
  s16 pa, pb, pc, pd;   // affine matrix (.8 fixed)
  s32 dx, dy;            // affine displacement (.8 fixed)
} BGAFF_EX;

pa= sx*cos(alpha)>>8;       pb= -sx*sin(alpha)>>8;
pc= sy*sin(alpha)>>8;       pd= sy*cos(alpha)>>8;
dx= px - (pa*qx + pb*qy);
dy= py - (pc*qx + pd*qy);


<plug shamelessness="100%">
If you want to know why these are the necessary steps, see Tonc:affine matrix and affine backgrounds (especially eq 4). For a little more about BIOS calls in general, BIOS calls
</plug>