how to use memory efficiently and faster

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
how to use memory efficiently and faster
by on (#3402)
i was wondering how to manage memory in the way so when games that have mappers i dont have to copy directly to the cpu mem, let say an example in MMC3: if we do a STA $8000, we are changing the control register, but the value its not written to the real $8000 mem, i mean if this value changes it compromises code execution.
In other words: how to threat memory so i dont have to swap in and out an entire block from mapper memory to the one the cpu uses.

by on (#3404)
It involves pointers.

For each 4 KB of address space, store the start address of the ROM bank mapped into that address space.

by on (#3491)
If your OS supports mmap() functionality, and the memory page size is small enough, you can malloc() 64KiB of memory, and mmap() the appropriate emulated PRG ROM pages and RAM in.

by on (#3492)
My advice:

Every Read/Write should be done through a function (with the acception of zero page reads/writes, which can always be done with RAM directly, as Zero page will always be RAM).

Set up several Read and Write functions which cover different areas of addressing space. For example:

Read_RAM (for $0000-1FFF)
Read_2xxx (for $2000-3FFF)
Read_4xxx (for $4000-4FFF)
Read_OpenBus (for $5000-5FFF)
Read_SRAM (for $6000-7FFF on supported games)
Read_PRG (for $8000-FFFF)

Keep 16 function pointers for reading, and 16 function pointers for writing, each which covers a 4k page of addressing space.

Code:
// make the pointers
ReadProc   ReadMemory[0x10];

// set up the pointers
ReadMemory[0x0] = Read_RAM;
ReadMemory[0x1] = Read_RAM;
ReadMemory[0x2] = Read_2xxx;
...
ReadMemory[0xF] = Read_PRG;



When the game performs a Read/Write, simply call the function which represents that area of addressing space. You can do this easily by pulling the high digit of the address and using it as an index (simple right shift by 12)

Code:
#define CPU_READ(adr)   ReadMemory[(adr) >> 12](adr)


This provides many benefits:

1) You can avoid doing several if-else chains for every read
2) When games adjust addressing space (by disabling WRAM, or putting RAM @ $8000 and up, or other weird things), this can easily be accomidated by changing the function pointer.
3) Mappers can easily catch their register writes by having their own write function and changing function pointers so that it gets called whenever the game writes there.



PRG/CHR swapping can be done easily by keeping one large buffer which holds ALL the game's PRG/CHR data.. and by keeping several pointers to represent PRG/CHR banks.

Code:
u8 nPRGBuffer[ size_of_games_prg ]; /* this should be allocated dynamically on ROM load with malloc() or new[] or whatever */

u8* pPRG[8];  // 8 pointers, each represents 4k of PRG space


With that above code -- each of the 8 pPRG pointers represents a 4k page of PRG. pPRG[0] would be cpu$8000, pPRG[1] would be cpu$9000, etc. To work this into the above mentioned Read_PRG function:

Code:
u8 Read_PRG(u16 adr)
{
  return pPRG[(adr >> 12) - 8][adr & 0x0FFF];
}


That will return the appropriate byte from the appropriate bank which was swapped in.

With this method, bankswapping can be done VERY easily by just changing a few pointers:

Code:
void Swap8kPRG(int where, int page)
{
  page *= 0x2000;
  pPRG[where] = &nPRGBuffer[ page ];
  pPRG[where + 1] = &nPRGBuffer[ page + 0x1000 ];
}


This allows you to swap PRG without having to copy large chunks of memory. 4k banks are the max size you should go with, as the smallest swap size is 4k (NSFs -- I don't know of any actual ROMs which swap any less than 8k).

CHR can be done the same way -- only you should go with 1k or smaller banks (many games have 1k banks -- I think that's the smallest any game swaps -- although I heard rumors that the mapper being used for Grandtheftendo will have 512 byte swapping, so you may want to prepare for it).


This exact same logic can be applied to Nametable mirroring. Simply use 4 pointers for each nametable, and when the game changes mirroring modes, simply change your pointers to accomidate the new mode.

That's what I'd recommend. I'd be happy to answer Qs or clarify if needed.

by on (#3637)
thxs disch, i have just saved the post for future implementation (dont you get scared if i ask a doubt to you again about this topic) :)

by on (#3679)
Quote:
// make the pointers
ReadProc ReadMemory[0x10];


how do i define a function pointer type?
i Mean how do i obtain "ReadProc"?


[/code]

by on (#3680)
Quote:
// make the pointers
ReadProc ReadMemory[0x10];


how do i define a function pointer type?
i Mean how do i obtain "ReadProc"?

by on (#3681)
Anes wrote:
Quote:
// make the pointers
ReadProc ReadMemory[0x10];


how do i define a function pointer type?
i Mean how do i obtain "ReadProc"?


there is good info at http://function-pointer.org/

by on (#3684)
You can take out the "REGPARM" bits if you like. It usually is a bit faster than passing variables on the stack, but some C compilers will occassionally produce bad code when optimizations are enabled.

Code:
#ifdef MSVC
#define REGPARM __fastcall
#else
#define REGPARM
#endif

typedef void (REGPARM *WriteProc)(uint32 A, uint8 V);
typedef uint8 (REGPARM *ReadProc)(uint32 A);

WriteProc WriteMemory[0x10];
ReadProc ReadMemory[0x10];

uint8 REGPARM ReadRAM(uint32 A)
{
 return(RAM[A & 0x7FF]);
}

void REGPARM WriteRAM(uint32 A, uint8 V)
{
 RAM[A & 0x7FF] = V;
}

void SomeFunction(void)
{
 ReadMemory[0] = ReadMemory[1] = ReadRAM;
 WriteMemory[0] = WriteMemory[1] = WriteRAM;

 WriteMemory[0x123 >> 8](0x123, 0x45);
 printf("%02x\n", ReadMemory[0x123 >> 8](0x123));
}

by on (#3685)
Ack, that should be >> 12, not >> 8. x_x