4.5.9 RTS Trick For Dummies

puppydrum64

Active member
I'm attempting to use the RTS trick described here to make my game run more quickly when drawing sprites. I got it to work for my player 1 object but the game crashes as soon as I leave the title screen. I couldn't make sense of the debug process when I ran the program one CPU cycle at a time, up to the crash. Here is the debug recording, maybe someone can watch what happens to the stack and see where I went wrong. I will also provide the code I used to create my sprite drawing script.


Code:
LDA gameHandler
    AND #%01000000
    BEQ doDrawThisSprite
    JMP doneDrawingThisSprite
doDrawThisSprite:
    ;;;; loaded into x is the object calling this routine.
    LDA Object_x_hi,x
    SEC
    SBC camX
    STA tempA
   
    LDA Object_y_hi,x
    STA tempB
   
    LDA Object_type,x
   
    JSR DrawSprite_opcode_launcher
   
    RTS
   
DrawSprite_opcode_launcher:
    TAX
    LDA DrawSprite_opcode_table+1,x
    PHA
    LDA DrawSprite_opcode_table,x
    PHA
    RTS
   
DrawPlayer1:
DrawSprite tempA, tempB, #$00, #%00100000
    LDA tempA
    CLC
    ADC #$08
    STA temp1 ;;equals tempA plus 8
DrawSprite temp1, tempB, #$01, #%00100000
    LDA tempB
    CLC
    ADC #$08
    STA temp2 ;; equals tempB plus 8
DrawSprite tempA, temp2, #$10, #%00000000
DrawSprite temp1, temp2, #$11, #%00000000
RTS

DrawPlayer2:

DrawSprite tempA, tempB, #$00, #%00000001
            LDA tempA
            CLC
            ADC #$08
            STA temp1 ;;equals tempA plus 8
DrawSprite temp1, tempB, #$01, #%00000001
            LDA tempB
            CLC
            ADC #$08
            STA temp2 ;; equals tempB plus 8
DrawSprite tempA, temp2, #$10, #%00000001
DrawSprite temp1, temp2, #$11, #%00000001
RTS


DrawMonsterBullet:
LDA vBlankTimer
        AND #%00000100
        BEQ +isEvenFrame
            ;; is Odd Frame
            DrawSprite tempA, tempB, #$21, #%00000010
            JMP doneDrawingThisSprite
        +isEvenFrame
        ;; Draw Alien Weapon, Game Object 2
        DrawSprite tempA, tempB, #$20, #%00000010
rts

DrawMissile1:
DrawSprite tempA, tempB, #$02, #%00000000
rts

DrawMissile2:
DrawSprite tempA, tempB, #$02, #%00000001
RTS

DrawObject5:
rts
DrawObject6:
rts
DrawObject7:
rts
DrawObject8:
rts
DrawObject9:
rts
DrawObjectA:
rts
DrawObjectB:
rts
DrawObjectC:
rts
DrawObjectD:
rts
DrawObjectE:
rts
DrawObjectF:
rts
DrawAlien:
DrawSprite tempA, tempB, #$80, #%00000010
            LDA tempA
            CLC
            ADC #$08
            STA temp1 ;;equals tempA plus 8
        DrawSprite temp1, tempB, #$81, #%00000010
            LDA tempB
            CLC
            ADC #$08
            STA temp2 ;; equals tempB plus 8
        DrawSprite tempA, temp2, #$90, #%00000010
        DrawSprite temp1, temp2, #$91, #%00000010
rts
DrawAlienShooter:
        DrawSprite tempA, tempB, #$80, #%00000011
            LDA tempA
            CLC
            ADC #$08
            STA temp1 ;;equals tempA plus 8
        DrawSprite temp1, tempB, #$81, #%00000011
            LDA tempB
            CLC
            ADC #$08
            STA temp2 ;; equals tempB plus 8
        DrawSprite tempA, temp2, #$90, #%00000011
        DrawSprite temp1, temp2, #$91, #%00000011
rts
       

doneDrawingThisSprite:
rts

Code:
TextLibrary_lo:
    .db #$00
TextLibrary_hi:
    .db #$00   
CheatCodeTable1:
    .db #$02, #$02, #$02, #$01, #$01, #$01, #$02, #$01
DrawSprite_opcode_table:
    .word DrawPlayer1-1
    .word DrawPlayer2-1
    .word DrawMonsterBullet-1
    .word DrawMissile1-1
    .word DrawMissile2-1
    .word DrawObject5-1
    .word DrawObject6-1
    .word DrawObject7-1
    .word DrawObject8-1
    .word DrawObject9-1
    .word DrawObjectA-1
    .word DrawObjectB-1
    .word DrawObjectC-1
    .word DrawObjectD-1
    .word DrawObjectE-1
    .word DrawObjectF-1
    .word DrawAlien-1
    .word DrawAlienShooter-1
 

Knietfeld

Member
I've never heard of this trick before, seems pretty handy, thanks!

As repayment, I don't know if it will solve everything but you've got

LDA Object_type,x

JSR DrawSprite_opcode_launcher

RTS

DrawSprite_opcode_launcher:
TAX
...
When what you need is


LDA Object_type,x
ASL ;to double it since you're looking at a table of two byte addresses.

TAX ; this doesn't have to be moved, it's just how I would do it so the x stuff is all together.

JSR DrawSprite_opcode_launcher

RTS

DrawSprite_opcode_launcher:
...

That really might be all it is. Player one would work because 0x2 is still 00 but then of it tried to do any other object type it'd be running code off of who knows where.
 

puppydrum64

Active member
I've never heard of this trick before, seems pretty handy, thanks!

As repayment, I don't know if it will solve everything but you've got

LDA Object_type,x

JSR DrawSprite_opcode_launcher

RTS

DrawSprite_opcode_launcher:
TAX
...
When what you need is


LDA Object_type,x
ASL ;to double it since you're looking at a table of two byte addresses.

TAX ; this doesn't have to be moved, it's just how I would do it so the x stuff is all together.

JSR DrawSprite_opcode_launcher

RTS

DrawSprite_opcode_launcher:
...

That really might be all it is. Player one would work because 0x2 is still 00 but then of it tried to do any other object type it'd be running code off of who knows where.
That worked! Thank you!
 

TakuikaNinja

Active member
Another optimizasion: Change the JSR call (to the opcode launcher) to JMP and remove the RTS after it.
This works since the opcode launcher is the last thing executed in that routine.
 

puppydrum64

Active member
Another optimizasion: Change the JSR call (to the opcode launcher) to JMP and remove the RTS after it.
This works since the opcode launcher is the last thing executed in that routine.
I thought you had to JSR into your opcode launcher or else you would RTS into an unknown part of code
 

TakuikaNinja

Active member
I thought you had to JSR into your opcode launcher or else you would RTS into an unknown part of code
Yes, you're correct about that in most cases.
However, if the call to the opcode launcher is the last thing a subroutine does (which it does in your case), it's more efficient to directly jump to the opcode launcher.
The nesdev wiki explains this much better than I can:
nesdev Wiki - Avoid a JSR + RTS chain

In fact, you can even remove the opcode launcher call and the RTS entirely in this routine, since the code for it is immediately after your subroutine.
Here's what the code leading up to the opcode launcher call would look like after doing this:

Code:
    LDA gameHandler
    AND #%01000000
    BEQ doDrawThisSprite
    JMP doneDrawingThisSprite
doDrawThisSprite:
    ;;;; loaded into x is the object calling this routine.
    LDA Object_x_hi,x
    SEC
    SBC camX
    STA tempA
  
    LDA Object_y_hi,x
    STA tempB
  
    LDA Object_type,x
    ASL ; to double it since you're looking at a table of two byte addresses.
    TAX ; value of x becomes the offset
    ; directly go into the opcode launcher since this is the last thing in the routine
DrawSprite_opcode_launcher:
    LDA DrawSprite_opcode_table+1,x
    PHA
    LDA DrawSprite_opcode_table,x
    PHA
    RTS ; this goes to the address pushed to the stack
 
Top Bottom