Subject: [stella] Is there a relationship between NUSIZ and HMOVE? From: Dusty Reichwein <badpandabear@xxxxxxxxxx> Date: Fri, 1 Jul 2005 01:46:12 -0400 |
Hi everyone, I'm perplexed by some behavior I've been seeing from the TIA and I was hoping someone here could enlighten me about what the heck's going on. I've been working on a display kernel that varies the NUSIZ width of a sprite on every scan line, so I can have sprites with a small but higher detailed top and a large but less detailed bottom. To do this, I also have to do an HMOVE to move the bottom portion of the sprite to the left so it appears in the correct relationship with the top portion. However, I have a problem. When the sprite is at the left most portion of the screen, the scan lines where the NUSIZ values change appear to be moved too far to the left and get out of sync with the rest of the sprite.. As the sprite moves to the right, the scan lines realign and the sprite starts to look normal. Once it scrolls off the right edge of the screen and back onto the left edge, the scrolled left edge has the same problem. I've attached a test program I wrote to test this problem. It shows four variations on displaying the same sprite: Top Sprite: NUSIZ0 at cycle 74, HMOVE on cycle 3. Second Sprite: NUSIZ0 at cycle 71, HMOVE on cycle 74 Third Sprite: NUSIZ0 at 76, HMOVE at cycle 3 Fourth Sprite: HMOVE at 74, NUSIZ0 at 1 The top two look okay when at the left edge of the screen. When moved to the right edge (using the joystick with this test program), the problem shows up on the left edge of the screen when the sprite scrolls over. (Both sprites also look stretched when they reach the point NUSIZ0 is set, but that's to be expected) The second two show the problem more clearly. I was thinking that the problem was caused by setting NUSIZ0 too close to or after the HMOVE, but I'm confused by why it is different when doing an early HMOVE vs a normal HMOVE. Perhaps its the position on the scan line that matters and not the time between setting NUSIZ0 and HMOVE? Note that none of these problems happen in Stella or Z26, although they do show another problem where an early HMOVE seems to be applied immediately, unlike on the real hardware. This led me to worry that maybe I'm using an unreliable feature of the TIA. So I tested it on several consoles. Specifically a 4 switch Vader, heavy sixer, light sixer, 2600 jr, 7800, gemini, 2600s, CCE supergame, and a 5200 using the 2600 adapter (phew). I got the same behavior on each, so I'm pretty sure its consistent for all NTSC versions of the TIA. Sorry for this long post, but this problem has me confused. Can someone here enlighten me on what exactly is going on? Also, would someone be willing to test this on a PAL Atari 2600? Thanks, Dusty Archives (includes files) at http://www.biglist.com/lists/stella/archives/ Unsub & more at http://stella.biglist.com [demime 1.01d removed an attachment with a content-type header it could not parse.] [Content-Type: data; name="multisize_sprites_ntsc.bin"] [demime 1.01d removed an attachment with a content-type header it could not parse.] [Content-Type: data; name="multisize_sprites_pal.bin"] PROCESSOR 6502 INCLUDE vcs.h INCLUDE macro.h ;;;;;;;;;;;;;;;;;;;;;; Place constants here ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PAL = 0 NTSC = 1 COMPILE_VERSION = NTSC IF COMPILE_VERSION == PAL VBLANK_TIMER = 44 SCREEN_SIZE = 284 OVERSCAN_COUNT = 30 BACKGROUND = $D0 FOREGROUND = $FF ELSE VBLANK_TIMER = 44 ;; was 54 SCREEN_SIZE = 228 OVERSCAN_COUNT = 36 BACKGROUND = $80 FOREGROUND = $FF ENDIF ;;; The Atari 2600 RAM area. SEG.U Ram ORG $80 spriteXPos ds 1 frameCounter ds 1 direction ds 1 tempVar1 ds 1 ; Macro to Position an object horizontally ; Inputs: A = Desired pixel position (0-159) ; Macro Argument: Desired object to be positioned (0-4) ; scanlines: Exactly one scanline is consumed ; Caller must call 5 cycles into scanline. Example: ; STA WSYNC ; SLEEP 5 ; POS_OBJECT ; ; Outputs: Y = unchanged ; A = The fine adjust value used ; X = the "remainder" of the division by 15 minus an additional 15. ; control is returned at the start of the next scanline. ; Credits: Originally from a post on the Stella List from R. Mundschau. ; This was taken from the AtariAge 2600 Programming For Newbies forum ; where it was posted by Andrew Davie. It has been modified based ; on suggestions from Eric Ball to avoid a 1 cycle penalty when ; crossing a page boundary. It has also been converted into a macro ; to avoid function call overhead MAC POS_OBJECT .WHICH SET {1} sec ; 07 Set the carry flag so no borrow will be applied during the division. .divideby15 sbc #15 ; 09 Waste the necessary amount of time dividing X-pos by 15! bcs .divideby15 ; 11/12 16/21/26/31/36/41/46/51/56/61 tax ; 2 lda fineAdjustTable,x ; 4 cycles (Table setup guarantees no crossing page boundary) sta HMP0 + .WHICH ; 3 sta RESP0 + .WHICH ; 3 (23/28/33/38/43/48/53/58/63/68/73) ;; End the scanline. If A was > 150 there is just enough cycles ;; to do this STA WSYNC ;; Double check Positioning: ;; If A = 0: ;; STA RESP0,X happens at 23 cycles ;; STA HMP0,X is Left 6 (-15 in lookup table) ;; TIA clock position is 23*3 + 5 -6 = 68 = correct ;; If A = 14: ;; STA RESP0,X happens at 23 cycles ;; STA HMP0,X is Right 8 (-1 in lookup table) ;; TIA clock position is 23*3+5 +8 = 82 = correct ;; If A = 15: ;; STA RESP0,X happens at 28 cycles ;; STA HMP0,X is Left 6 (-15 in lookup table) ;; TIA clock position is 29*3+5 - 6 = 83 = correct ;; If A = 159: ;; STA RESP0,X happens at 73 cycles ;; STA HMP0,X is Right 3 (-6 in lookup table) ;; TIA clock position is 73*3+5 +3 = 227 = correct .notLessThan120 ENDM ;;; Start of 4K rom space SEG ROM ORG $F000 ;;; Where code starts executing Start CLEAN_START ;; Initialize sprite position and speed LDA #0 STA direction LDA #0 STA spriteXPos ;; Set port A to input for reading from the joystick STA SWACNT ;;; Start of a screen display FrameStart VERTICAL_SYNC JSR VerticalBlankCalc JSR ScanlineKernel JSR Overscan JMP FrameStart ;;; Performs game logic during vertical blank VerticalBlankCalc SUBROUTINE ;; Setup timer LDA #VBLANK_TIMER STA TIM64T ;; Setup background color LDA #BACKGROUND STA COLUBK ;; Setup sprite color LDA #FOREGROUND STA COLUP0 ;; Move sprite 1 pixel every 4 frames ;; if the joystick is pressed left or right. LDA frameCounter AND #$03 BNE .noMove ;; see if bit 6 is 0 (meaning left is pressed) LDA #%01000000 BIT SWCHA BNE .skipMoveLeft LDA #-1 STA direction JMP .move .skipMoveLeft LDA #%10000000 ;See if bit 7 0 (meaning rigt is pressed) BIT SWCHA BNE .noMove LDA #1 STA direction JMP .move .noMove LDA #0 STA direction .move LDA spriteXPos CLC ADC direction CMP #160 BNE .lessThan LDA #159 JMP .endMove .lessThan CMP #-1 BNE .endMove LDA #0 .endMove STA spriteXPos ;; Increment frame counter INC frameCounter ;; Now wait for the end of vertical blank .loop LDA INTIM BNE .loop STA WSYNC STA VBLANK ;;; Note A will be 0, so this turns of VBLANK RTS ;;; Draws the visible scanlines ScanlineKernel SUBROUTINE ;; Draw scanlines here. ;; This is sample code that doesn't do anything ;; Skip 10 scanlines LDX #10 .loop STA WSYNC DEX BNE .loop ;; Scanline 11 ;; Display a multi size sprite using normal HMOVE LDY #<BattleShipType LDA spriteXPos JSR DisplayMultiSizeSprite ;; A few blank lines to seperate sections STA WSYNC STA WSYNC STA WSYNC ;; Display a multisize sprite using early HMOVE LDY #<BattleShipType LDX spriteXPos JSR DisplayMultiSizeSprite2 ;; Now display a multi size sprite using normal HMOVE, and a NUSIZ store ;; that is too close to the HMOVE and will cause errors on real hardware LDY #<BattleShipType LDA spriteXPos JSR DisplayMultiSizeSprite3 ;; A few blank lines to seperate sections STA WSYNC STA WSYNC STA WSYNC ;; Now display a multi size sprite using early HMOVE, and a NUSIZ store ;; that is after the HMOVE and will cause errors on real hardware LDY #<BattleShipType LDX spriteXPos JSR DisplayMultiSizeSprite4 ;; Finish out the rest of the screen LDX #SCREEN_SIZE - 135 .loop2 STA WSYNC DEX BNE .loop2 RTS ;;; Set VBlank for overscan Overscan SUBROUTINE LDA #2 STA VBLANK LDY #OVERSCAN_COUNT .loop STA WSYNC DEY BNE .loop RTS ;; Displays a multi size sprite ;; Arguments ;; A horizontal position of sprite (0-159) ;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize ;; Returns on cycle 14 DisplayMultiSizeSprite SUBROUTINE ;; Do Horizontal positioning STA WSYNC SLEEP 5 POS_OBJECT 0 STA HMOVE STA WSYNC ;; Delay to get the loop to start on a consistent ;; point on the scanline LDX #4 ; (2,2) .delay1 DEX ; (2,4+(n-1)*5) BNE .delay1 ; (3 or 2, ends at 6+(n-1)*5) 21 when n = 4 SLEEP 3 ; (3, 24) .spriteLoop LDA SizeData,Y ; (4,28) STA HMP0 ; (3,31) AND #$0F ; (2,33) LDX #7 ; (2,35) .delay2 DEX ; (2,37+(n-1)*5) BNE .delay2 ; (3 or 2, 39+(n-1)*5) with n = 4, ends at 69) SLEEP 2 ; (3,71) sta NUSIZ0 ; (3,74) SLEEP 2 ; (2,76) sta HMOVE ; (3,3) LDX SpriteData,Y ; (4,07) SLEEP 7 ; (7,14) STX GRP0 ; (3,17) DEY ; (2,19) ;; check for end of sprite AND #$08 ; (2,21) BEQ .spriteLoop ; (3,24) or (2,23) ;; End of sprite, clear out motion register and graphics STA WSYNC LDA #0 ;(3,3) STA HMP0 ;(3,6) STA GRP0 ;(3,9) RTS ;(6,14) ;; Displays a multi size sprite using an early hmove ;; Arguments ;; X horizontal position of sprite (0-159) ;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize2 ;; Returns on cycle 14 DisplayMultiSizeSprite2 SUBROUTINE ;; Do Horizontal positioning LDA HorzTable,X STA HMP0 STA WSYNC AND #$0F ; (2,2) STX tempVar1 ; (3,5) TAX ; (2,7) SLEEP 12 ;; for timing (12,19) .loop1 DEX ; (2,21+5*X) BPL .loop1 ; (2,23+5*X) on final loop STA RESP0 ; (3,26+5*X) ;; if X was > 150, it is now the next line. If it wasn't it is now ;; at most 71 cycles on the positioning line. To determine if a WSYNC ;; is needed we'll check against 135, because that gives enough time ;; to actually do the WSYNC ;; X < 135 | 136 <= X <= 150 | X > 150 LDA tempVar1 ; (3,69) | (3,74) | (3,3) CMP #136 ; (2,71) | (2,76) | (2,5) BCS .noSync ; (2,73) | (3,3) | (3,8) ;; X < 135 Theres just enough time for the WSYNC STA WSYNC .noSync ;; It is now 0, 3, or 8 cycles on this scanline ;; Do a WSYNC to get to a consistent location on a scanline STA WSYNC ;; Now we must do a HMOVE on cycle 73 LDX #12 ;; (2,2) ;; Loop needs to take 61-2 = 59 cycles ;; (X-1)*5 + 4 = 59 ;; X = 12 .loop2 DEX ;; (2,4,9,.................) (2,59) BNE .loop2 ;; (3,7,12,17,22,27,32...52,57) (2,61) LDA SizeData2,Y ;; (4,65) AND #$0F ;; (2,67) .spriteLoop sta.w NUSIZ0 ;; (4, 71) STA HMOVE ;; (3,74) LDX SpriteData,Y ;; (4,2) STX GRP0 ;; (3,5) ;; check for end of sprite AND #$08 ;; (2,7) BNE .endSprite ;; (2,9) or (3,10) DEY ;; (2,11) LDA SizeData2,Y ;; (4,15) ;; It's been 17 since the HMOVE, need to wait until 24 after SLEEP 7 ;; (7,22) STA HMP0 ;; (3,25) AND #$0F ;; (2,27) ;; waste some time so that the next HMOVE is at the right cycle LDX #6 ;; (2,29) ;; Loop will take (6-1)*5+4 = 29 cycles .loop3 DEX ;; (2,31,36) (2,56) BNE .loop3 ;; (3,34,39,...54)(2,58) SLEEP 6 ;; (6,64) JMP .spriteLoop ;; (3,67) .endSprite LDA #80 STA HMP0 STA WSYNC LDA #00 STA GRP0 STA NUSIZ0 RTS ;; Displays a multi size sprite, using an NUSIZ store that ;; causes errors on real hardware ;; Arguments ;; A horizontal position of sprite (0-159) ;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize ;; Returns on cycle 14 DisplayMultiSizeSprite3 SUBROUTINE ;; Do Horizontal positioning STA WSYNC SLEEP 5 POS_OBJECT 0 STA HMOVE STA WSYNC ;; Delay to get the loop to start on a consistent ;; point on the scanline LDX #4 ; (2,2) .delay1 DEX ; (2,4+(n-1)*5) BNE .delay1 ; (3 or 2, ends at 6+(n-1)*5) 21 when n = 4 SLEEP 3 ; (3, 24) .spriteLoop LDA SizeData,Y ; (4,28) STA HMP0 ; (3,31) AND #$0F ; (2,33) LDX #7 ; (2,35) .delay2 DEX ; (2,37+(n-1)*5) BNE .delay2 ; (3 or 2, 39+(n-1)*5) with n = 4, ends at 69) SLEEP 4 ; (4,73) STA NUSIZ0 ; (3,76) ;; Set size before HMOVE sta HMOVE ; (3,3) LDX SpriteData,Y ; (4,07) SLEEP 7 ; (7,14) ;; just here for timing cause I'm too lazy ;; to absorb it in a delay loop STX GRP0 ; (3,17) DEY ; (2,19) ;; check for end of sprite AND #$08 ; (2,21) BEQ .spriteLoop ; (3,24) or (2,23) ;; End of sprite, clear out motion register and graphics STA WSYNC LDA #0 ;(3,3) STA HMP0 ;(3,6) STA GRP0 ;(3,9) RTS ;(6,14) ;; Displays a multi size sprite using an early hmove, and a NUSIZ store ;; that will cause problems on real hardware ;; Arguments ;; X horizontal position of sprite (0-159) ;; Y 8 bit pointer to sprite offset in SpriteData and SpriteSize2 ;; Returns on cycle 14 DisplayMultiSizeSprite4 SUBROUTINE ;; Do Horizontal positioning LDA HorzTable,X STA HMP0 STA WSYNC AND #$0F ; (2,2) STX tempVar1 ; (3,5) TAX ; (2,7) SLEEP 12 ;; for timing (12,19) .loop1 DEX ; (2,21+5*X) BPL .loop1 ; (2,23+5*X) on final loop STA RESP0 ; (3,26+5*X) ;; if X was > 150, it is now the next line. If it wasn't it is now ;; at most 71 cycles on the positioning line. To determine if a WSYNC ;; is needed we'll check against 135, because that gives enough time ;; to actually do the WSYNC ;; X < 135 | 136 <= X <= 150 | X > 150 LDA tempVar1 ; (3,69) | (3,74) | (3,3) CMP #136 ; (2,71) | (2,76) | (2,5) BCS .noSync ; (2,73) | (3,3) | (3,8) ;; X < 135 Theres just enough time for the WSYNC STA WSYNC .noSync ;; It is now 0, 3, or 8 cycles on this scanline ;; Do a WSYNC to get to a consistent location on a scanline STA WSYNC ;; Now we must do a HMOVE on cycle 73 LDX #12 ;; (2,2) ;; Loop needs to take 61-2 = 59 cycles ;; (X-1)*5 + 4 = 59 ;; X = 12 .loop2 DEX ;; (2,4,9,.................) (2,59) BNE .loop2 ;; (3,7,12,17,22,27,32...52,57) (2,61) LDA SizeData2,Y ;; (4,65) AND #$0F ;; (2,67) .spriteLoop SLEEP 4 ;; (4, 71) STA HMOVE ;; (3,74) STA NUSIZ0 ;; (3,77 or 1 on next scanline) LDX SpriteData,Y ;; (4,5) STX GRP0 ;; (3,8) ;; check for end of sprite AND #$08 ;; (2,10) BNE .endSprite ;; (2,12) or (3,13) DEY ;; (2,14) LDA SizeData2,Y ;; (4,18) ;; It's been 22 since HMOVE, SLEEP 2 more ;; to make sure it's okay to set HMP0 SLEEP 2 ;; (2,20) STA HMP0 ;; (3,23) AND #$0F ;; (2,25) ;; waste some time so that the next HMOVE is at the right cycle LDX #7 ;; (2,27) ;; Loop will take (7-1)*5+4 = 34 cycles .loop3 DEX ;; (2,29,34, (2,59) BNE .loop3 ;; (3,32,37,...57)(2,61) SLEEP 3 ;; (3,64) JMP .spriteLoop ;; (3,67) .endSprite LDA #80 STA HMP0 STA WSYNC LDA #00 STA GRP0 STA NUSIZ0 RTS ALIGN 256 SpriteData BattleShip .byte %11111111 .byte %11111111 .byte %00111110 .byte %01111111 .byte %00011100 .byte %00111110 ;; This is first quad sized line .byte %00011100 ;; This is the first (and only) double sized line .byte %01110110 .byte %01110110 .byte %00110110 .byte %00110100 .byte %00110000 .byte %01111000 .byte %00100000 BattleShipType .byte %00100000 ALIGN 256 SizeData ;; BattleShip .byte %00001111 .byte %00000111 .byte %00000111 .byte %00000111 .byte %00000111 .byte %01110111 .byte %01100101 .byte %00000000 .byte %00000000 .byte %00000000 .byte %00000000 .byte %00000000 .byte %00000000 .byte %00000000 .byte %00000000 ALIGN 256 ;; Size data with HMPx values setup for early hmove SizeData2 .byte %10001111 .byte %10000111 .byte %10000111 .byte %10000111 .byte %10000111 ;; .byte %00100111 ;; original data from fireone sprite ;; .byte %11010101 .byte %11110111 ;; modified data so it will look like normal HMOVE sprite .byte %11100101 .byte %10000000 .byte %10000000 .byte %10000000 .byte %10000000 .byte %10000000 .byte %10000000 .byte %10000000 .byte %10000000 ;----------------------------- ; This table converts the "remainder" of the division by 15 (-1 to -15) to the correct ; fine adjustment value. ; This table originally came from code posted to the Stella list by R. Mundschau, ; and then posted to the AtariAge 2600 Programming For Newbies Forum by Andrew Davie. ; Per suggestion by Eric Ball, the table has been moved to be just before a page boundary ; so that no penalty is generated when accessing the table. ; The adjustments have been moved right by 1 so that they go from Left 6 to Right 8. ; This allowed for a position of 0 to map to a pixel position of 68 TIA clocks. ORG $FEF1 fineAdjustBegin DC.B %01100000 ; Left 6 -15 DC.B %01010000 ; Left 5 -14 DC.B %01000000 ; Left 4 -13 DC.B %00110000 ; Left 3 -12 DC.B %00100000 ; Left 2 -11 DC.B %00010000 ; Left 1 -10 DC.B %00000000 ; No movement. -9 DC.B %11110000 ; Right 1 -8 DC.B %11100000 ; Right 2 -7 DC.B %11010000 ; Right 3 -6 DC.B %11000000 ; Right 4 -5 DC.B %10110000 ; Right 5 -4 DC.B %10100000 ; Right 6 -3 DC.B %10010000 ; Right 7 -2 DC.B %10000000 ; Right 8 -1 fineAdjustTable EQU fineAdjustBegin - %11110001 ; NOTE: %11110001 = -15 ;; This table holds the coarse and fine grained positioning values for ;; a given X pos (0-159) The high nibble is the fine grained value, ;; the low nibble is the coarse grained value ALIGN 256 HorzTable .byte $70, $60, $50, $40, $30, $20, $10, $00, $F0, $E0 .byte $D0, $C0, $B0, $A0, $90, $80, $61, $51, $41, $31 .byte $21, $11, $01, $F1, $E1, $D1, $C1, $B1, $A1, $91 .byte $81, $62, $52, $42, $32, $22, $12, $02, $F2, $E2 .byte $D2, $C2, $B2, $A2, $92, $82, $63, $53, $43, $33 .byte $23, $13, $03, $F3, $E3, $D3, $C3, $B3, $A3, $93 .byte $83, $64, $54, $44, $34, $24, $14, $04, $F4, $E4 .byte $D4, $C4, $B4, $A4, $94, $84, $65, $55, $45, $35 .byte $25, $15, $05, $F5, $E5, $D5, $C5, $B5, $A5, $95 .byte $85, $66, $56, $46, $36, $26, $16, $06, $F6, $E6 .byte $D6, $C6, $B6, $A6, $96, $86, $67, $57, $47, $37 .byte $27, $17, $07, $F7, $E7, $D7, $C7, $B7, $A7, $97 .byte $87, $68, $58, $48, $38, $28, $18, $08, $F8, $E8 .byte $D8, $C8, $B8, $A8, $98, $88, $69, $59, $49, $39 .byte $29, $19, $09, $F9, $E9, $D9, $C9, $B9, $A9, $99 .byte $89 ;; At 151 there is a discontinuity because the RESP0 is on the next ;; line .byte $4A, $3A, $2A, $1A, $0A, $FA, $EA, $DA, $CA ;;; Interrupt vector table ORG $FFFA .word Start ;; NMI .word Start ;; Reset .word Start ;; IRQ
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Thread | Re: [stella] Is there a relationshi, Manuel Rotschkar | |
Date | Re: [stella] Is there a relationshi, Manuel Rotschkar | |
Month |