It is currently Thu Dec 14, 2017 7:50 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Mon May 23, 2011 12:29 pm 
Offline

Joined: Sat Jan 23, 2010 11:41 pm
Posts: 1161
I wonder, how it is possible to have vertical scrolling in bottom part of a split screen. It is said in the docs that writes in Y scroll register only applied in the beginning of a frame and can't be changed mid-frame, and emulators acts accordingly, so you can't just change Y scroll after split.

Together with PPU_ADDR registers it is easy to get split screen with vertical scroll in the upper part and static bottom part. It does not work vice versa because of the reason mentioned above, and PPU_ADDR only allow to set vertical position at a character row, not a pixel row.

However, there are examples that it is possible to have static upper part with vertical scrolling in the bottom part, like in Ninja Gaiden 3 (vertical part of first level). Is some trick was used there to achieve the result?


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 23, 2011 12:45 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
Yes there is a trick : The trick is to write the VRAM adress of the tile you want to "scroll" to to $2006. (This works for both horizontal and vertical scrolling by the way).
The con is that you have a granularly of a tile in both axis, so you have to compensate for that by using $2005 (horizontally) and by using timed code (vertically).

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 23, 2011 12:50 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
You can do it with the good old $2005/$2006 combo (example code). With this you can change the scroll to any location you want anywhere in the screen, just make sure that the final writes happen during HBlank for a clean split.

What Bregalad described is the solution that was used before the $2005/$2006 behavior exploited above was known, and it has some limitations.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 23, 2011 12:57 pm 
Offline

Joined: Sat Jan 23, 2010 11:41 pm
Posts: 1161
What Bregalad said is actually a third of my first post in another words, except for 'timed code', which is new info, but not really explains anything.

Thanks for the example, I'll check it.

Edit: the example does not work for me. Maybe I'm doing something wrong or don't understand something. Which idea is behind this code, how and why it should work? I.e. how vertical pixel-perfect position achieved?


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 23, 2011 4:56 pm 
Offline

Joined: Mon Sep 27, 2004 2:57 pm
Posts: 1248
The "address" you write to $2006 isn't actually an address, but a series of counters that determine various things, such as the current tile, the current nametable, the current row of the current tile, etc.

When you use $2007, the various counters you set with $2006 are lined up to behave like an actual address, for accessing the PPU's address space.

When the PPU is rendering however, the various bits of the value you write to $2006 will take on a different meaning, such that $2006 no longer behaves like an address. Trust me, I was confused by this for a long time. :P

Here's what the 2006-2005-2005-2006 trick does:

Code:
2006/1 --vv NNVV
2005/2 VVVV Vvvv
2005/1 HHHH Hhhh
2006/2 VVVH HHHH

The first 2006 write can only set the fine vertical scrolling to 0-3, because the two most significant bits of the value you write are ignored (replaced by 0). The only thing you need to worry about is the NN bits (which select the nametable you want), because all of the other bits will be overwritten by the next write you'll make in the next step.

The "second" write to 2005 will set the fine vertical scroll value (v) correctly, overwriting whatever value you used in the first 2006 write. This write also sets the coarse vertical scrolling (V), again overwriting whatever you used for the two V bits in the previous step. Be aware, the three lower V bits will be overwritten by the last step, so what you set them to here doesn't matter.

The "first" write to 2005 will set the fine horizontal scroll value (h). The coarse horizontal scroll (H) will be completely overwritten by the next step, so don't worry about what value you use here.

The final write, the second write to 2006, will set the coarse horizontal scrolling (H), and will overwrite the lower 3 bits of the coarse vertical scrolling (V). After this write, all of the values you've written will take effect.

So, removing all of the extra bits that get overwritten, this is what you write to 2006/2005/2005/2006:
Code:
2006/1 ---- NN-- (nametable select)
2005/2 VV-- -vvv (upper two bits of coarse V scroll, all bits of fine V scroll)
2005/1 ---- -hhh (fine horizontal scrolling) (takes effect immediately)
2006/2 VVVH HHHH (lower three bits of coarse V scroll, all bits of coarse H scroll)


Correct me if I'm wrong, but only the last two writes need to be in h-blank. The first two writes won't have any effect on the screen.


Last edited by Drag on Mon May 23, 2011 4:57 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Mon May 23, 2011 4:56 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
Shiru wrote:
the example does not work for me. Maybe I'm doing something wrong or don't understand something.

It has worked fine for me and for tepples, maybe you are doing something wrong. Keep in mind that ScrollX and ScrollY are 16-bit variables, although only 9 bits are actually used. Also note that the low byte of the Y scroll should always be between 0 and 239, otherwise you'll be rendering attribute tables as if they were name tables.

Quote:
Which idea is behind this code, how and why it should work? I.e. how vertical pixel-perfect position achieved?

It's based on the information contained in the famous "The Skinny on NES Scrolling" document. The fact is that it's possible to change the scroll with 2 $2006 writes, but since this register was not meant for setting the scroll there's 1 bit (which is part of the Y scroll) that gets cleared when $2006 is written to. Loopy's document describes which bits get set and cleared when different PPU registers are written to, so through a combination of $2005 and $2006 writes it's possible to set all the scroll bits. This requires some bit shifting to make sure each bit is where it's supposed to be.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 12:27 am 
Offline

Joined: Sat Jan 23, 2010 11:41 pm
Posts: 1161
Drag wrote:
Code:
2006/1 ---- NN-- (nametable select)
2005/2 VV-- -vvv (upper two bits of coarse V scroll, all bits of fine V scroll)
2005/1 ---- -hhh (fine horizontal scrolling) (takes effect immediately)
2006/2 VVVH HHHH (lower three bits of coarse V scroll, all bits of coarse H scroll)


Thanks, this explained everything and I got it to work. Would be nice to have this in the wiki.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 12:43 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
Well to each their own. I MUCH prefer the "adress" approach, it makes a lot more sense to me.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 5:16 am 
Offline
User avatar

Joined: Wed Oct 15, 2008 11:50 am
Posts: 939
It just doesn't allow you to do what the OP needs. But yea, the address approach is much less brain bleeding.

And this info is already on the wiki: PPU scrolling


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 5:45 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
Bregalad wrote:
Well to each their own. I MUCH prefer the "adress" approach, it makes a lot more sense to me.

The problem is that you can't always do a clean split with it... Even if you use timed code to set the vertical scroll in one of 8 scanlines you'll get scrolling artifacts in the split area.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 1:27 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
Quote:
The problem is that you can't always do a clean split with it... Even if you use timed code to set the vertical scroll in one of 8 scanlines you'll get scrolling artifacts in the split area.

I think you can always do a clean split with it. If there is glitches then it has something to do with the fine-tuned scanline timing where you do the writes, and not the write themselves.
I think only writes to $2005/1 and $2006/2 takes effect during the frame. That way, if you only write to $2006 you only have one write to fit in HBlank instead of two, which makes the thing easier (hmm I guess).

If it's the 8-pixel granularity you are talking about then this is not a problem. In many cases it won't be a problem at all because you just don't need a lower granuarly. For example in Ninja Gainden 3 vertical rooms, there is a black unused bar on the bottom of the status bar, and this is used to hide this.
Also, I think you can specify fine scroll values 0 to 3 by using bits 12 and 13 of the adress (it's a mystery why), so you get some kind of fine scrolling, only values 4 to 7 aren't availble.

Even if you HAD to acess values 4 to 7, I think you could get away with $2005, $2005, $2006, $2006 writes. The first two would work as usual (but the second $2005 write would get ignored), then the last two would just be there so that the second $2005 writes actually takes effect.
Of course the $2006 adress has to match with the $2005 scroll value to avoid glitches.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 1:52 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3968
The first 2006 write will change the horizontal nametable for the next scanline, but other than that, only the last 2 writes have any visible effect.
So you can complete the last two writes very quickly by using different registers for the Store instruction.

You can do something like this:
Code:
lda value1 ;9 pixels time
sta $2006  ;12 pixels time
lda value2 ;9 pixels time
sta $2005 ;12 pixels time
lda value3 ;9 pixels time
ldx value4 ;9 pixels time
sta $2005 ;12 pixels time
stx $2006 ;12 pixels time

So the last two writes have no problem fitting inside hblank time.

If you use horizontal/single screen mirroring, changing the horizontal nametable also does nothing at all.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 24, 2011 3:54 pm 
Online
Site Admin
User avatar

Joined: Mon Sep 20, 2004 6:04 am
Posts: 3487
Location: Indianapolis
For other uses of the trick, such as scrolling every scanline of the display individually, it's helpful to have tables. These 2 have always worked for me, so far:
http://www.parodius.com/~memblers/nes/vram_hi.bin
http://www.parodius.com/~memblers/nes/vram_lo.bin
Those tables in ROM, of course you would want aligned to 256-byte boundaries so it doesn't cross pages when reading.

Feel free to use it, and this code if it helps any. The delay at the beginning would need to be rewritten to adapt it to another program. This is JSR'd to from NMI, and the code preceding it must be branch-less (or predictable). This is NTSC timed, loops once per scanline for 162 lines. "scroll_table" is a table in RAM that lists the vertical scroll position for every scanline. So if the table in RAM is just a backwards count for example, you would see an upside-down background.

Code:
scroll_timing_code:

                  ldx #3
:
                  ldy #$A3
:
                  dey
                  bne :-
                  dex
                  bne :--

                  nop
                  nop
                  nop

                  ldy #0
   scanline_loop:
                  lda scroll_table,y            ; 4    4
                  tax                           ; 2    6
                  lda vram_addr_hi,x            ; 4    10
                  sta $2006                     ; 4    14
                  stx $2005                     ; 4    18
                  lda #0                        ; 2    20
                  sta $2005                     ; 4    24
                  lda vram_addr_lo,x            ; 4    28
                  sta $2006                     ; 4    32
                                                ;
                  lda irrational_counter        ; 3    35
                  clc                           ; 2    37
                  adc #$55                      ; 2    39
                  sta irrational_counter        ; 3    42
                  bcc @nowhere                  ; 2/3  44.6
   @nowhere:                                    ;
                                                ;
                  ldx #11                       ; x*5 + 1
   :                                            ;
                  dex                           ;
                  bne :-                        ;

                  nop
                  nop
                  nop
                                                ;
                  iny                           ; 2    46.6
                  cpy #162                      ; 2    48.6
                  bne scanline_loop             ; 3    51.6
                                                ;
                                                ; 2    53.6
                                                ; 59
                  rts


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 29, 2011 3:04 pm 
Offline

Joined: Sat Jan 23, 2010 11:41 pm
Posts: 1161
I got a major problem with this trick on real HW: it just does not work properly. I haven't seen how it looks by myself, but basically it jitters for two tiles horizontally when scroll offset is greater than zero. Usual scroll works just fine (but no vertical scroll in this case, of course).

Maybe I did something wrong? It works in all the emulators I've tried:

Code:
lda <GAME_CAM_X+1
   and #1
   asl a
   asl a
   sta PPU_ADDR   ;---- NN--
   lda <GAME_CAM_Y
   sta PPU_SCROLL   ;VV-- -vvv
   lda <GAME_CAM_X
   and #7
   sta PPU_SCROLL   ;---- -hhh
   lda <GAME_CAM_X
   lsr a
   lsr a
   lsr a
   ora #$80
   sta PPU_ADDR   ;VVVH HHHH
   rts


GAME_CAM_X is a word 0..256 (not greater), GAME_CAM_Y is 0..7.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 29, 2011 8:49 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
Shiru wrote:
I got a major problem with this trick on real HW

This has to be a problem with your implementation, because my ROM works perfectly fine in all of my consoles, and lots of other people have used this trick with success.

Quote:
Code:
lda <GAME_CAM_X+1
   and #1
   asl a
   asl a
   sta PPU_ADDR   ;---- NN--
   lda <GAME_CAM_Y
   sta PPU_SCROLL   ;VV-- -vvv
   lda <GAME_CAM_X
   and #7
   sta PPU_SCROLL   ;---- -hhh
   lda <GAME_CAM_X
   lsr a
   lsr a
   lsr a
   ora #$80
   sta PPU_ADDR   ;VVVH HHHH
   rts

Your second PPU_ADDR write looks pretty incomplete... Why are you writing %100HHHHH to it? And why is GAME_CAM_Y only 0..7? I really can't see what you're trying to accomplish here... with these limitations you obviously can't be aiming for "free" scrolling, so I don't really know what to say. I also don't see any reason for the "and #7"... Is there any reason for you to not write all 8 bits?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 19 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group