Jump to content
karri

New ideas in saving memory

Recommended Posts

Well, new ideas for me at least...

While programming my next masterpiece for the 30 years competition I found out that I got completely stuck with too little RAM and got a bit desperate.

But now I found some great solutions to free much more RAM.

In large games you need some kind of loader that loads in the next level. Just loading in a lots of levels eat up lots of RAM as the loader is typically sitting in the resident segment.

The big "idea" that works is to create a separate segment called "keyhandler" that handles the joystick and the keyboard. This keyhandler is needed all the time except when you are loading in new content. So the "keyhandler" and the "loader" can reside in the same segment in RAM.

Amazing... Isn't it?

 

In the resident segment:

 

typedef unsigned char (*fptr)();
extern fptr loader (unsigned char index);

static unsigned char loadandexec(unsigned char index)
{
    fptr lvl;

    lynx_load((int)&LOADER_FILENR);
    lvl = loader(index);
    lynx_load((int)&KEYHANDLER_FILENR);
    return lvl();
}

ret = loadandexec(1); // To execute level 1

Note that the 'loader' is activated for loading the data and getting the entry point for the code. After this I load in the keyboard/joystick support that is needed to run the level.


In the loader:
 

fptr loader (unsigned char index)
{
  switch (index) {
    default:
    case 1:
        lynx_load((int)&LVL01_FILENR);
        return lvl01;
    case 2:
        lynx_load((int)&LVL02_FILENR);
        return lvl02;
...

}

 

The cool thing here is that all my levels have main routines like

unsigned char lvl01() {

...

    return ret;

}

 

And my loader will then load in the right level from the cart and also pick the correct start address of the level routine.

 

Edited by karri
typo
  • Like 1

Share this post


Link to post
Share on other sites

That's a great suggestion Karri! Are you also using TGI in your masterpiece? One of the things I've found is that I use very few TGI features, so keeping the full TGI around is wasteful. Maybe a minimal TGI implementation is a good idea to have?

  • Like 2

Share this post


Link to post
Share on other sites

Yes. I am using a tiny version of TGI. In order to compile a tiny TGI I just changed the start of the standard and compiled it in. In my case I have a file "mini-tgi.s" that defines the TGI object. There is no "JUMPTABLE" segment. Instead I use "CODE". This version will automatically replace the stock version.


 

        .include        "zeropage.inc"
        .include        "extzp.inc"

        .include        "tgi-kernel.inc"
        .include        "tgi-error.inc"

        .include        "lynx.inc"
        .export         _lynx_160_102_16

        .macpack        generic

; ------------------------------------------------------------------------
; Header. Includes jump table and constants.

.segment        "CODE"

_lynx_160_102_16:

; First part of the header is a structure that has a magic and defines the
; capabilities of the driver

        .byte   $74, $67, $69           ; "tgi"

 

  • Like 2

Share this post


Link to post
Share on other sites

That would be nice. There is also a ComLynx driver that would shrink a lot be omitting all obscure baud speeds.

The nice thing is that the drivers can easily be made static by adding a calling label that points to the jump table.

The activation call

tgi_install(&tgi_static_stddrv);

works because actually "tgi_static_stddrv" is just

 

        .export    _tgi_static_stddrv
        .import    _lynx_160_102_16

.rodata

_tgi_static_stddrv := _lynx_160_102_16

In the same way the joystick driver is
 

        .export    _joy_static_stddrv
        .import    _lynx_stdjoy

.rodata

_joy_static_stddrv := _lynx_stdjoy

We should probably create a similar static driver shortcut for the serial driver also. It is already compiled as lynx-comlynx.ser so we need to write the static support. I will probably fix this later today. It will most likely look as this:

        .export    _ser_static_stddrv
        .import    _lynx_comlynx

.rodata

_ser_static_stddrv := _lynx_comlynx

Then you can create your own "ser" driver with whatever functionality suitable for your gaming needs.

Edited by karri

Share this post


Link to post
Share on other sites

Coding on lynx sooner or later everyone needs to find some free memory somewhere, and I tryed some extreme solutions lately.

 

For example in Griel's quest i'm using a large PCM sample during the intro and had no other option than loading it in one of the framebufer.

 

The tricky thing was to have always the right framebuffer available when loading the sample.

 

Here is a dirty piece of code that does the magic (not the best possible, but it works)

 

	while (page<3 && !reset)
	{
		lastKey=0;
		keypressed = 1;
		while (tgi_busy()) ;
		tgi_setpalette(b_pal);
		if(page==1)
		{
			tgi_clear();
			tgi_updatedisplay ();
			while (tgi_busy()) ;
		}
		tgi_clear();
		sp_img.vpos=0;
		sp_img.hpos=16;
		sp_img.data=spStory

; tgi_sprite(&sp_img); if(page==0) { tgi_storyxy (35, 70, "Every thousand years the"); tgi_storyxy (35, 78, "Enemy awakes and comes"); tgi_storyxy (35, 86, "to our World to destroy it."); tgi_storyxy (35, 94, "A dark age begins now..."); } else if(page==1) { tgi_storyxy (35, 70, "Only a blessed Knight with"); tgi_storyxy (35, 78, "a pure heart and the aid of"); tgi_storyxy (35, 86, "the Sangraal would be able"); tgi_storyxy (35, 94, "to defeat the Evil Lord."); } else { tgi_storyxy (35, 70, "Sir Griel has been selected"); tgi_storyxy (35, 78, "to begin the Quest for the"); tgi_storyxy (35, 86, "Sangraal and save the world."); tgi_storyxy (35, 94, "from total annihilation."); } tgi_updatedisplay (); while (tgi_busy()) ; fade_in(story_pal, 100); if(page==0) { tgi_clear(); tgi_sprite(&sp_img); tgi_storyxy (35, 70, "Every thousand years the"); tgi_storyxy (35, 78, "Enemy awakes and comes"); tgi_storyxy (35, 86, "to our World to destroy it."); tgi_storyxy (35, 94, "A dark age begins now..."); tgi_updatedisplay (); while (tgi_busy()) ; tgi_setviewpage(0); tgi_setdrawpage(1); play_sample(0); } while(!lastKey && !reset) if(!keypressed) lastKey = checkInput(); else if(!checkInput()) keypressed = 0; fade_out(100); page++; }

 

Share this post


Link to post
Share on other sites

You could always just set the viewpage and drawpage to the same index 0. Then the lower page (page 1) is free. The new forum cut the code in half. Really strange.

 

In On Duty I use streaming PCM directly from cart. That is also possible if you can guarantee that nobody needs cart access.

Share this post


Link to post
Share on other sites

I need double buffering, but if the screen is not changing I can use the inactive.

 

About streaming from cart, I'm going to implement it because I need some sampld sfx without freezing the game.

Share this post


Link to post
Share on other sites

The important thing is to not use sei/cli in the interrupt handler. You should be able to extract the relevant part from HandyMusic driver. Just comment out sei/cli commands. With sei/cli masking enabled I was not able to keep up the speed on the real hardware.

        .interruptor    _HandyMusic_PCMMain,15
_PlayPCMSample:
        PHA
        ;sei                                    ; Kill IRQs just for a bit to...
        LDA HandyMusic_Disable_Samples
        beq permissionGranted                   ; Make sure we're allowed to play
        PLA
        ;cli
        rts
permissionGranted:
        LDA#$FF
        STA HandyMusic_Channel_NoWriteBack      ; Capture Channel 0
        STZ $FD25
        STZ $fD0D                               ; Disable IRQ3 in case sample is already playing
        ;cli
        LDA $FD40
        STA Sample_PanBackup
        LDA #$FF
        STA $FD40
        STA _Sample_Playing                     ; Sample is playing
        PLA
        clc
;**********************
; Load File Directory *
;**********************
        ;ADC#<_SAMPLES_FILENR                   ; Sample File Offsets
        ;ldx #0
        ;jsr _openn                             ; Load Directory
;***********************
; Parse File Directory *
;***********************
ParseDir00:
;       LDA _FileExecFlag                       ; Check to see if exec flag is set
;       bne ParseDir01                          ; in order to toggle AUDIN
;       LDA __iodat                             ; (For 1MB Card control)
;       AND#%11101111
;       STA __iodat
;       bra ParseDir02
;ParseDir01:
;       LDA __iodat
;       ORA#%00010000
;       STA __iodat
;ParseDir02:
        lda _FileStartBlock
        sta _FileCurrBlock                   ; startblock
        jsr lynxblock
        lda _FileBlockOffset
        eor #$FF
        tax
        lda _FileBlockOffset+1
        eor #$FF
        tay
        jsr lynxskip0
;********************
; Setup Timer 3 IRQ *
;********************
        LDA#125
        STA $fd0c; T3 Backup (125)
        STA $fd0e; T3 Current
        LDA#$D8
        STA $fd0d; T3 Mode (~8000Hz Reload)
        rts


 

;****************************************************************
; PCMSample_IRQ:                                                *
;       Streams a single sample from the cartridge, playing     *
;       it through the direct volume register of channel 0.     *
;       Automatically disabled when playback is finished.       *
;****************************************************************
PCMSample_IRQ:
;echo "HandyMusic PCM IRQ Address: %HPCMSample_IRQ"
;***************************
;* Read one byte from Cart *
;***************************
        ;PHP
        ;PHA
        DEC _FileFileLen
        bne KeepReading
        DEC _FileFileLen+1
        beq PlayBackDone
KeepReading:
        jsr ReadByte    ; Read byte
        sta $fd22       ; Store to Direct Volume reg
        bra ExitIRQ
PlayBackDone:
        STZ $fd0d ; Kill IRQ, Sample is finished.
        STZ _Sample_Playing ; Sample is not playing
        LDA Sample_PanBackup
        STA $FD40
        STZ HandyMusic_Channel_NoWriteBack
ExitIRQ:
        ;PLA
        ;PLP
        rts
;**********************************************************
;* Fetch one byte from cart, reselecting block if needed. *
;**********************************************************
ReadByte:
;       LDA _FileExecFlag
;       AND#2
;       beq LoROMRead
;       lda $fcb3
;       bra IncCartByte
;LoROMRead:
        lda $fcb2
IncCartByte:
        inc _FileBlockByte
        bne BailOut
        inc _FileBlockByte+1
        bne BailOut
        jmp lynxblock
BailOut:
        rts

I did not get the directory stuff to work so in the game I set the seek to the right spot before the call.

 

extern void __fastcall__ openn(int filenr);
                  openn((int)&SAMPLES_FILENR);
                  PlayPCMSample(0);

 

Share this post


Link to post
Share on other sites

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...