Author Topic: Project Idea: Interrupt Routine that draws sprites and tilemaps  (Read 3650 times)

0 Members and 1 Guest are viewing this topic.

Offline Hot_Dog

  • CoT Emeritus
  • LV12 Extreme Poster (Next: 5000)
  • *
  • Posts: 3006
  • Rating: +445/-10
    • View Profile
The original Nintendo system has a graphics processor that automatically draws tilemaps and sprites.  All you need to do is feed it sprites and X/Y coordinates, and it draws everything for you.  That means you can write code without having to worry about creating your own sprite routines.

I thought that it would be cool to have something like that for the Ti-83+.  An interrupt routine could draw sprites and tilemaps found in a certain area of RAM, so the user would only need to copy data to that area of RAM.

Offline Geekboy1011

  • The Oneironaut
  • Donator
  • LV11 Super Veteran (Next: 3000)
  • ***********
  • Posts: 2031
  • Rating: +119/-2
  • Dream that Awakening dream
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #1 on: March 28, 2012, 05:41:52 pm »
wow i actually made something like this for TiX

ill post the code up for it later but it draws stuff based on a table located in ram. you give it a sprite loc and x,y and grey level i think its been a while.


but other than that table info it handles everything in a simple interrupt routine

EDIT: not gonna paste my code its a cluster fuck and i dont even know what i was thinking when i wrote it.

but this would be a nice side project to work on :D


« Last Edit: March 28, 2012, 05:58:04 pm by Geekboy1011 »

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #2 on: March 31, 2012, 04:20:45 pm »
Hmm, if this gets done, you should keep two buffers. The first buffer is the buffer you use to draw. You scan it and compare each byte to the other buffer, if it is different, copy the byte to the other buffer and draw the sprite. This will save speed if the program only needs to change a few tiles at a time and will only lose a few cycles when the whole map needs to be updated.

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #3 on: July 16, 2013, 11:39:21 am »
Necrobump o_O

I just googled "how fast are sprite drawing routines TI-84+" to see if anybody has already done some calculations when I found this page again and it actually happens to be relevant. I completely forgot about this, but I am glad I found it!

In one of my projects, I have it so that my interrupt routine and the interrupt table are in the app (I know this sounds like a bad idea and I need to be sure not to swap out the page without turning off interrupts). The interrupt is actually what I use to handle LCD updating and drawing the tilemap. All I have to do is write the tileset pointer, tilemap location, width, and height to a spot in RAM, and as well, the current (X,Y) offsets into the map. Then set a flag saying that it is in tilemap mode, and another flag saying that the tilemap needs to be rendered and the interrupt will render it. Since the interrupt is also handling the keyboard, my code uses a bunch of halt instructions, ensuring that the tilemaps are updated smoothly. The other tasks include animating the tiles and if any tiles move to a new frame, the tilemap is automatically updated on the LCD. This is an example code and the screenshot of it:
Code: [Select]
Continue:
     call whilekey
     call loadtestmap
moveloop:
     halt
     ld a,(curkey)
     or a
     jr z,moveloop
     cp 15
     jp z,softreset
     cp 9
     jr nz,testmove
testtileevent:
       jr moveloop
testmove:
     ld hl,(yoffset)
     ld de,(xoffset)
     dec a
     jr nz,$+3
       inc hl
     dec a
     jr nz,$+3
       dec de
     dec a
     jr nz,$+3
       inc de
     dec a
     jr nz,$+3
       dec hl
     ld (yoffset),hl
     ld (xoffset),de
     jr moveloop

In order to make this code better for more general use, it would have to have the interrupt reside in RAM and make it easy for the user to link their own interrupt code.

I am still working on refining the tilemap routine and sprite routines (as you could probably tell from the Google search). However, here is all of the code in my interrupt currently, as well as relevant subroutines and a little documentation (I didn't have it prepared):
Spoiler For Interrupt:
Here is the code:
Code: [Select]
Interrupt:             ;4242h
     push bc
     push de
     push hl
     push af
; interrupt stuff
;Note that I am using push/pop instead of the exx and ex af,af'
;instructions so that I can use shadow registers for other
;intensive routines
; Tasks to handle:
;   Sound ?
;   LCD refreshing, if a specific flag is set
;   Key ports
     
     call GetKey
     ld hl,843Fh      ;valid for the 83+/84+ calcs. This is where the OS stores its key values, too
     ld (hl),a
     inc l
     cp (hl)
     jr z,LoadSameKey
     ld (hl),a
     inc l
     ld (hl),128
     jp KeysUpdated
LoadSameKey:
     inc l
     dec (hl)
     jr z,ReloadKey
     xor a
     jp KeysUpdated
ReloadKey:
     ld (hl),32
KeysUpdated:     
     inc l
     ld (hl),a    ;key debounced
     bit mapmode,(iy+textflags)
     jr z,mapupdated
       ld hl,8000h                    ;this is where I have an LUT to store the current sprite frames and timers
AnimUpdLoop:
       dec (hl)
       jr nz,NextAnimUpd
         inc l
         set drawmap,(iy+asmflags)
         ld bc,Tiledata
         ld e,(hl)
         ld d,0        ;DE is the tile number
         ex de,hl
         add hl,hl
         inc l
         add hl,hl     ;tile data is 4 bytes, .dw loc \ .db new \ .db time
         add hl,bc
         ld a,(hl)
         ld (de),a
         dec e
         ld l,a
         ld h,0
         add hl,hl
         inc l
         add hl,hl
         inc l
         add hl,bc
         ld a,(hl)
         ld (de),a
         ex de,hl
NextAnimUpd:
       inc l
       inc l
       jr nz,AnimUpdLoop
MapUpdated:
     bit forcedkey,(iy+asmflags)
     jr z,EndInterrupt
;code for key macros
EndInterrupt:
     bit drawmap,(iy+asmflags)
     call nz,Tilemap
     bit lcdupdate,(iy+asmflags)
     call nz,UpdateLCD
     ld a,8
     out (3),a
     ld a,10
     out (3),a
     ld a,15
     out (3),a
     pop af
     pop hl
     pop de
     pop bc
     ei
     ret
TileMap:
     res lcdupdate,(iy+asmflags)
     ld hl,(yoffset)
     ld (ycur),hl
     ex de,hl
     ld bc,(mapwidth)
     call DE_Times_BC
     ld de,(tilemapdata)
     add hl,de
     ld de,(xoffset)
     add hl,de
     ld (tileptr),hl
     ld hl,(mapwidth)
     ld bc,-12
     add hl,bc   ;amount to add to pointer every Y increment
     ld (adjust),hl
     xor a
     ld (y),a
yloop:
     xor a
     ld (x),a
     ld hl,(xoffset)
     ld (xcur),hl
xloop:
     ld hl,(xcur)
     ld bc,(mapwidth)
     or a
     sbc hl,bc
     jr c,$+7
     ld hl,blacktile
     jr drawtile
     ld hl,(ycur)
     ld bc,(mapheight)
     or a
     sbc hl,bc
     jr c,$+7
     ld hl,blacktile
     jr drawtile
loadtile:
     ld hl,(tileptr)
     ld e,(hl)
     ld d,0
     ld hl,curframe
     add hl,de
     add hl,de
     inc hl
     ld e,(hl)
     ld d,0
     ex de,hl
     add hl,hl
     add hl,hl
     ld de,tiledata
     add hl,de
     ld e,(hl)
     inc hl
     ld d,(hl)
     ex de,hl
drawtile:
;HL points to the sprite
     ex de,hl
     ld a,(y)
     add a,a \ add a,a
     add a,a \ add a,a
     ld c,a
     ld b,0
     ld h,b \ ld l,c
     add hl,hl
     add hl,bc
     add hl,hl
     ld bc,(DrawBufPtr)
     add hl,bc
     ld a,(x)
     add a,l
     ld l,a
     jr nc,$+3
     inc h
;HL points to where it gets drawn
;DE is the sprite layer
     ld bc,12
     ld a,8
       ex de,hl   ; 32
       ldi        ;128
       ex de,hl   ; 32
       add hl,bc  ; 88
       inc c      ; 32
       dec a      ; 32
       jr nz,$-7  ; 91
;now we need to increment X,tileptr,xcur
     ld hl,(tileptr)
     inc hl
     ld (tileptr),hl
     ld hl,(xcur)
     inc hl
     ld (xcur),hl
     ld a,(x)
     inc a
     ld (x),a
     cp 12
     jp nz,xloop
     ld hl,(tileptr)
     ld bc,(adjust)
     add hl,bc
     ld (tileptr),hl
     ld hl,(ycur)
     inc hl
     ld (ycur),hl
     ld a,(y)
     inc a
     ld (y),a
     cp 8
     jp nz,yloop


UpdateLCD:    ;(chooses monochrome or 3-level gray based on a flag)
     ld hl,(primarybufptr)
     bit gray,(iy+asmflags)
     jr z,monochrome
     exx
     ld hl,89F0h+asmflags   ;89F0h is where the OS keeps IY
     ld a,(hl)
     ld bc,256*graybitmask+55h
     xor b
     ld (hl),a
     and b
     jr z,$+4
     rlc c

     ld de,12
     ld hl,(secondarybufptr)
     exx
     ld de,12
     ld a,20h
col3:
     out (16),a
     ex af,af'
     ld b,40h
row3:
     exx         ;4
     ld a,(hl)   ;7
     and c       ;8
     rlc c       ;8
     add hl,de   ;11
     exx         ;4
     or (hl)     ;7
     add hl,de   ;11
     bit rvideo,(iy+asmflags)  ;23
     jr z,$+3 \ cpl            ;11
     out (17),a  ;11
     djnz row3    ;13
     dec h
     dec h
     dec h
     inc hl
     exx
     dec h
     dec h
     dec h
     inc hl
     exx
     in a,(16) \ rlca \ jr c,$-3
     ex af,af'
     inc a
     cp 2Ch
     jr nz,col3
     ret


monochrome:
     ld de,11
     ld a,20h
col:
     out (16),a
     push af
     ld bc,4011h
row:
     in a,(16) \ rlca \ jr c,$-3
     outi
     add hl,de
     jr nz,row
     in a,(16) \ rlca \ jr c,$-3
     pop af
     inc a
     dec h
     dec h
     dec h
     inc l
     cp 2Ch
     jr nz,col
     res lcdupdate,(iy+asmflags)
     ret
;===============================================================
GetKey:
;===============================================================
;Outputs:
;     a is a value from 0 to 56 that is the keypress
;    bc is also the key press
;     d has a bit reset, the rest are set (this is the last key group tested)
;     e is a divided by by 8
;    hl is not modified
;===============================================================
          ld bc,0             ;010000    ;10
          ld de,$7FFF         ;11FF7F    ;10
KeyLoop:                                 ;
            rlc d             ;CB02      ;8
            ld a,d            ;7A        ;4
            out (1),a         ;D301      ;12
            inc e             ;1C        ;4
            sub 7Fh           ;D67F      ;7
            ret z             ;C8        ;5|11
            nop               ;00        ;4
            in a,(1)          ;DB01      ;12
            inc a             ;3C        ;4
            jr z,KeyLoop      ;28EC      ;7|12
            dec a             ;3D        ;4
              inc c           ;0C        ;4
              rra             ;1F        ;4
              jr c,$-2        ;38FC      ;7|12
            ld a,e            ;7B        ;4
            rlca \ rlca \ rlca;070707    ;12
            add a,c           ;81        ;4
            ld c,a            ;4F        ;4
            ret               ;C9        ;10
The code I have for setting up the animation LUT (for the tile number and timer) is in this routine:
Code: [Select]
loadtestmap:
     ld hl,23
     ld (mapwidth),hl
     ld l,13
     ld (mapheight),hl
     ld l,0
     ld (xoffset),hl
     ld (yoffset),hl
     ld (ymapcoord),hl
     ld (xmapcoord),hl
     ld h,80h       ;HL = 8000h, this code loads the 128 tiles and their counters are all set to 1. The next interrupt sets them to 0, and updates them accordingly.
     xor a
       ld (hl),1
       inc l
       ld (hl),a
       inc a
       inc l
       jr nz,$-6
     ld hl,testmap
     ld (tilemapdata),hl
     set mapmode,(iy+textflags)
     ret
This code is still under construction and I am working out a lot of details. For example, I plan to have 3 layers-- the tilemap, and a masked layer on top of that (for things such as the player sprite and NPCs). I also have been thinking about storing to the graph buffer differently and organising it in columns, but I am still in the process of analysing the respective performance gains and hits. (which is what the original Google search was for). If anybody knows, I have 3297 t-states for my worst case drawing of an 8x8 sprite with XOR logic and 236 t-states for drawing a tile ^^. Shifting the LCD for smooth scrolling is what I am really worried about.

EDIT: Also, the tilemap and tileset:
Code: [Select]
testmap: ;23 wide, 13 tall
 .db 3,3,3,3,3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,3,3,3
 .db 3,0,0,1,1,1,0,0,0,0,0,4, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,0,0,1,1,1,2,2,2,0,0,3, 1,0,0,0,1,0,1,7,1,0,3
 .db 3,0,0,1,1,1,2,2,2,0,0,3, 7,0,0,0,7,0,0,1,0,0,3
 .db 3,5,5,5,0,0,2,2,2,0,0,3, 1,7,1,7,1,0,0,7,0,0,3
 .db 3,5,5,5,0,0,0,0,0,0,0,3, 7,0,0,0,7,0,0,1,0,0,3
 .db 3,5,5,5,0,0,0,0,0,0,0,3, 1,0,0,0,1,0,1,7,1,0,3
 .db 3,3,3,3,4,3,3,4,3,3,3,3, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,0,1,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,3,4,5,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,6,7,8,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,9,10,11,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,3
 .db 3,12,13,14,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,3

#define addtile(tile,evolve,dur) .dw tile \ .db (evolve-TileData)/4,dur

TileData:
tile_blank:    addtile(blank,  tile_blank,  0)
tile_flower1:  addtile(flower1,tile_flower2,16)
tile_grass:    addtile(grass,  tile_grass,  0)
tile_bush:     addtile(bush,   tile_bush,   0)     ;a small tree (8x8)
tile_vine:     addtile(vine,   tile_vine,   0)     ;instead of that silly tree to cut down
tile_water1:   addtile(water1, tile_water2,10)

tile_water2:   addtile(water2, tile_water1,10)
tile_flower2:  addtile(flower2,tile_flower1,16)


blank:
 .db 0,0,0,0,0,0,0,0
flower1:
 .db $44,$AA,$44,$00,$22,$55,$22,$00
flower2:
 .db $22,$55,$22,$00,$44,$AA,$44,$00
grass:
 .db $00,$66,$55,$33,$00,$66,$55,$33
bush:
 .db $18,$34,$4A,$85,$8B,$76,$18,$3C
vine:
 .db $91,$7E,$52,$89,$91,$4A,$7E,$89
water1:
 .db $44,$AA,$11,$00,$22,$55,$88,$00
water2:
 .db $11,$AA,$44,$00,$88,$55,$22,$00

BlackTile:
     .db $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF

Offline TIfanx1999

  • ಠ_ಠ ( ͡° ͜ʖ ͡°)
  • CoT Emeritus
  • LV13 Extreme Addict (Next: 9001)
  • *
  • Posts: 6173
  • Rating: +191/-9
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #4 on: July 16, 2013, 11:55:06 am »
Wow, that looks very nice Xeda! ^^

Offline Sorunome

  • Fox Fox Fox Fox Fox Fox Fox!
  • Support Staff
  • LV13 Extreme Addict (Next: 9001)
  • *************
  • Posts: 7920
  • Rating: +374/-13
  • Derpy Hooves
    • View Profile
    • My website! (You might lose the game)
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #5 on: July 16, 2013, 11:55:31 am »
wow, that's awesome O.O

THE GAME
Also, check out my website
If OmnomIRC is screwed up, blame me!
Click here to give me an internet!

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #6 on: July 16, 2013, 12:02:55 pm »
It's at 6MHz, too :) The other nice thing about this is that if the interrupt were activated during a BASIC program, it could work (it would slow down the BASIC code significantly, probably). The main difficulty would be in making an interface so that BASIC programs could access the appropriate memory areas, but that could be done with a parser hook.

Anyways, the goal of such a program is to make it very easy and straightforward to use sprites and tilemaps in assembly and I think this makes it pretty easy ;) You don't even need to call the tilemap routine!

Offline Streetwalrus

  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 3821
  • Rating: +80/-8
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #7 on: July 16, 2013, 12:21:41 pm »
That looks pretty awesome. :D
I guess this is a redraw every frame aligned map ?

Offline Xeda112358

  • they/them
  • Moderator
  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 4704
  • Rating: +719/-6
  • Calc-u-lator, do doo doo do do do.
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #8 on: July 16, 2013, 12:54:01 pm »
That looks pretty awesome. :D
I guess this is a redraw every frame aligned map ?
Thanks! And I do not know what you mean. It redraws whenever an animated tile goes to its next frame and it only draws to 8x8 regions (so there are 96 tile locations on the screen). For my purposes, this is enough (I will be using smooth-scrolling in the way Pokémon games did it by scrolling in a whole tile, 1 pixel at a time). However, for a project such as what Hot_Dog had in mind, it would be useful to have an option for drawing the map at any pixel offset.

Also, I just compared my sprite drawing routine to the one in Axe and my speed estimate was really far off (I massively overestimated how long it would take). Anyways, my method did turn out faster >.>

Offline Streetwalrus

  • LV12 Extreme Poster (Next: 5000)
  • ************
  • Posts: 3821
  • Rating: +80/-8
    • View Profile
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #9 on: July 16, 2013, 02:12:03 pm »
That looks pretty awesome. :D
I guess this is a redraw every frame aligned map ?
Thanks! And I do not know what you mean.
I meant that it redrew the whole map each time and that all sprites are aligned to the 12*8 grid. This is not the case apparently.

Offline Eiyeron

  • Urist McEiyolobster
  • LV10 31337 u53r (Next: 2000)
  • **********
  • Posts: 1430
  • Rating: +130/-10
  • (-_(//));
    • View Profile
    • Rétro-Actif : Rétro/Prog/Blog
Re: Project Idea: Interrupt Routine that draws sprites and tilemaps
« Reply #10 on: August 28, 2013, 04:45:47 pm »
Bump! How will you release that work when finished? Asm routines? Axiom? ASM program? I'm interessed and curious to see!