Non-fixed bank calling into other non-fixed bank via fixed?
Moderator: Moderators
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Non-fixed bank calling into other non-fixed bank via fixed?
Is it ever common for a non-fixed PRG bank to make a call or use data from another non-fixed bank using a routine in the fixed bank to retrieve the data or make the call? I was thinking this could be a potential way to further reduce duplication of data across PRG banks.
At one point, all of my game data was stored in one PRG bank at a time: music, level, sprites, animations, and entity code. Now, I've got sprites, animations and entity code in one bank and level and music data in another bank. However, sometimes duplication is still necessary in order to support multiple game data sets (for lack of a better term) which may have some of the same sprites/animations/entities. So I've been thinking about ways to reduce that duplication. Thoughts/advice?
At one point, all of my game data was stored in one PRG bank at a time: music, level, sprites, animations, and entity code. Now, I've got sprites, animations and entity code in one bank and level and music data in another bank. However, sometimes duplication is still necessary in order to support multiple game data sets (for lack of a better term) which may have some of the same sprites/animations/entities. So I've been thinking about ways to reduce that duplication. Thoughts/advice?
Of course a fixed bank can and does act as a trampoline between code in one bank and code in another. But if you have code that interacts with numerous banks of data, it's probably best to put that code in the fixed bank if possible.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
That's how my code works at present. All bankswitching is done from the fixed bank, and any code that runs within a non-fixed bank only accesses data already in the currently loaded fixed-bank. What I'm thinking about is an extreme case where perhaps you have almost all of your 16k PRG block filled with entity code, and it needs to access meta sprites or animations in another 16K PRG block that also could not fit into the fixed bank. Does this ever happen in practice or is this an extreme idea/case?
*edit* come to think of it, I actually do have a couple of special situations where this occurs. When an entity tests the map, it calls a routine in the fixed bank which switches out the non-fixed bank, does the map collision test, then restores the previous bank and returns to the entity which called it. I guess my real question is: how far have you seen this technique go in commercial games? Do you ever find functions which can marshall a call between non-fixed banks to any routine or retrieve any data? Or are they always specialized such as the case I described?
*edit* come to think of it, I actually do have a couple of special situations where this occurs. When an entity tests the map, it calls a routine in the fixed bank which switches out the non-fixed bank, does the map collision test, then restores the previous bank and returns to the entity which called it. I guess my real question is: how far have you seen this technique go in commercial games? Do you ever find functions which can marshall a call between non-fixed banks to any routine or retrieve any data? Or are they always specialized such as the case I described?
This is a valid thing to do, but I'd avoid doing it too much because the function call and the bankswitches add a lot of overhead.
I'm trying to do the following in order to avoid having a switchable bank using another switchable bank: The fixed bank contains the main game engine, because this is usually the one that needs frequent access to the other banks (usually for level data). To give more space to the main engine, code that doesn't need access to multiple banks (such as title screens, menus, PPU update routines, and so on) is put in a switchable bank.
I don't know if the 16KB of the fixed bank will be enough to hold the main engine when it's complete, but at least I do my best to keep unrelated stuff elsewhere.
I'm trying to do the following in order to avoid having a switchable bank using another switchable bank: The fixed bank contains the main game engine, because this is usually the one that needs frequent access to the other banks (usually for level data). To give more space to the main engine, code that doesn't need access to multiple banks (such as title screens, menus, PPU update routines, and so on) is put in a switchable bank.
I don't know if the 16KB of the fixed bank will be enough to hold the main engine when it's complete, but at least I do my best to keep unrelated stuff elsewhere.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
That's a good idea, thanks. Do you anticipate being able to fit all of your entity code into the fixed bank along with your engine? Since this is my first NES game, I have no way of knowing how much space my entity code will ultimately occupy. I went with the trampoline scheme described above to account for the possibility that the code could become quite large. I'll have to look into trying what you suggested and move some code out of the fixed bank that doesn't need to be there, and see how much I have left over. I guess the bottom line is, there's no single "correct" way of dealing with these issues, since any given choice represents some kind of trade off.
- neilbaldwin
- Posts: 481
- Joined: Tue Apr 28, 2009 4:12 am
- Contact:
I recently had to move a lot of my drawing routines into fixed ROM. So much handier now when adding new menus etc. Means I can stick the name tables in whichever bank I like and call the drawing routines with the relevant bank number. Probably common sense stuff but I'm learning all the time.
I wish you could bank-switch DPCM - it would make my life a lot easier
I wish you could bank-switch DPCM - it would make my life a lot easier
What is entity code? All Google can find are HTML character entities.Gradualore wrote:That's how my code works at present. All bankswitching is done from the fixed bank, and any code that runs within a non-fixed bank only accesses data already in the currently loaded fixed-bank. What I'm thinking about is an extreme case where perhaps you have almost all of your 16k PRG block filled with entity code
Yes. I know of three such call gates in Apple II products alone:how far have you seen this technique go in commercial games? Do you ever find functions which can marshall a call between non-fixed banks
- Apple IIe call gate bankswitches in an alternate ROM bank.
- Apple II ProDOS has the so-called "MLI" call gate that switches from the upper ROM (containing BASIC and the Monitor) to the RAM (containing ProDOS).
- The IIGS call gate switches to 65C816 native mode (which allows execution outside the first 64 KiB of memory) and looks up routines in a jump table. If you have a IIGS, try going into the Monitor and listing the code at $00/F89C.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
I was under the impression entity was a common term used on this forum to describe in-game objects that have a lifespan and some kind of intelligence/update routine. googling "nesdev entities" or "nesdev entity" comes up with several posts by me, and a few others using this term. I think I first adopted it after some discussions with Banshaku a while back. I was originally just going to call them "enemies" since I didn't have any plans for a non enemy entity, but later on I felt the more generic term was better.tepples wrote:What is entity code? All Google can find are HTML character entities.Gradualore wrote:That's how my code works at present. All bankswitching is done from the fixed bank, and any code that runs within a non-fixed bank only accesses data already in the currently loaded fixed-bank. What I'm thinking about is an extreme case where perhaps you have almost all of your 16k PRG block filled with entity code
Well there is many way it could be done, but you'd want to go into this order, the first being the best and the last being the worst :
1) Code and data related to it in one 16k bank
2) If 1) cannot be done because you have too much data, place code in the fixed bank and data related to it in multiple 16k banks
3) If 2) cannot be done because the fixed bank is aldready full, have code in a switched bank, and data related to it that exceed the switched bank being acedded trough a "trampoline" routine in the fixed bank (will be very slow !)
4) If it lags by using 3) because it was too slow, have your code repeated in multiple banks (at the same adress) so that it can bankswitch the data related to it quickly without using a trampoline
Another way to solve the problem is to use 32k banks (your reset, NMI, IRQ and bankswitching routines will HAVE to be done like point 4), so you can have code and related data in a 32k bank, wich is twice as large. However, if you still overflow that limit, you'll have to deal with something similar to point 4) in all cases. However, it is much less likely you'll need that because the banks are bigger.
1) Code and data related to it in one 16k bank
2) If 1) cannot be done because you have too much data, place code in the fixed bank and data related to it in multiple 16k banks
3) If 2) cannot be done because the fixed bank is aldready full, have code in a switched bank, and data related to it that exceed the switched bank being acedded trough a "trampoline" routine in the fixed bank (will be very slow !)
4) If it lags by using 3) because it was too slow, have your code repeated in multiple banks (at the same adress) so that it can bankswitch the data related to it quickly without using a trampoline
Another way to solve the problem is to use 32k banks (your reset, NMI, IRQ and bankswitching routines will HAVE to be done like point 4), so you can have code and related data in a 32k bank, wich is twice as large. However, if you still overflow that limit, you'll have to deal with something similar to point 4) in all cases. However, it is much less likely you'll need that because the banks are bigger.
Useless, lumbering half-wits don't scare us.
- MetalSlime
- Posts: 186
- Joined: Tue Aug 19, 2008 11:01 pm
- Location: Japan
The Guardian Legend does this a lot. Their CHR data for example is spread across several banks, and for engines that aren't in the fixed bank (title screen, password screen, ending, etc) they have to do some bankswap voodoo to get their graphics.
They have it streamlined down into a single subroutine that can handle all cases. When they want to call a routine from another unfixed bank, they embed the address and bank of the target subroutine as DATA after the call to the fixed bank helper routine, like this:
The switching routine itself builds a bridge back to the original bank using the stack and then reads the target address:
Just an example of how one commercial game handles this.
They have it streamlined down into a single subroutine that can handle all cases. When they want to call a routine from another unfixed bank, they embed the address and bank of the target subroutine as DATA after the call to the fixed bank helper routine, like this:
Code: Select all
JSR Crossbank_bridge ;this routine performs the switch
.byte $03 ;target bank
.word $8134-1 ;address of target subroutine (in RTS form)
lda whatever ;continue code in this bank upon return
cmp something
beq somewhere
Code: Select all
Crossbank_bridge:
STA tempA ;save A and Y
STY tempY
PLA ;pull return address off stack and
STA ptr1 ;store in a pointer variable for 3-byte data read.
CLC ;we also add 3 bytes and push it back on the stack,
ADC #$03 ;building a bridge back to where we started
TAY ;we add 3 so we return to the point AFTER the embedded
PLA ;data bytes
STA ptr1+1
ADC #$00
PHA
TYA
PHA
LDA current_bank ;push the current bank on the stack too
PHA
JSR Crossbank_Go ;perform the jump. JSR here is important.
;when the crossbank routine returns, it will return
;here
STA tempA
STY tempY
PLA ;pull the origin bank
JMP bankswitch ;JMP is important here. when bankswitch returns
;it will return to the proper place in the original bank
Crossbank_Go:
LDY #$03
LDA (ptr1),Y ;read target address in other bank
PHA ;address will be in the form of address-1 b/c
DEY ;of the RTS trick
LDA (ptr1),Y
PHA
DEY
LDA (ptr1),Y ;read target bank
STA current_bank ;bank switch and return to the target bank
TAY
LDA bank_table,Y ;UNROM bankswitch
STA bank_table,Y ;now we are in the target bank
LDA tempA ;restore vars
LDY tempY
RTS ;RTS to target routine
In games that use CHR-RAM there are usually many pages of tile data, and this is a good example of when it's OK to do these things, because the goal is to use a big amount of data at once, and probably isn't even done during gameplay, when CPU time is a very important resource. If a similar routine was used several times during gameplay to fetch small pieces of data, there would certainly be a significant impact on performance.
UNROM is easy to mod, replace the 74HC32 with a 74HC08 (replacing OR gates with AND gates) and it makes UNROM keep $8000-$BFFF fixed and $C000+ is banked. That's iNES mapper #180, for Crazy Climber, and it's the type of bank setup I used in that 512kB-sized 'Chipography' NSF.Banshaku wrote:You can with some specific mapper like the MMC3 and some under ones that I don't remember.neilbaldwin wrote:I wish you could bank-switch DPCM - it would make my life a lot easier