How to propertly scroll wide image?

For anything related to VDP (plane, color, sprite, tiles)

Moderators: BigEvilCorporation, Mask of Destiny

Post Reply
peekpoke
Interested
Posts: 37
Joined: Fri Feb 01, 2013 1:11 am

How to propertly scroll wide image?

Post by peekpoke » Fri Feb 01, 2013 2:32 am

Hi, all!

Im total newbie in programming. But very want to start learning. Sega Megadrive is a console from my childhood - so, i decded, why not combine those two things.

Already did simple hello world (Conway's Game of Life demo). Now want to understand how to scroll some wide background picture. But got difficulties with it, so, if its possible - i would be glad to get any help!

I have picture consisted of 230 x 28 tiles (8x8) (optmized a little, so used 4024 unique tiles), palette, map. Just want to try to scroll this picture horizontally. For simplifity, just scroll from left side to right side of this picture.

I decided to do it this way:
1) create map buffer equal to screen size+1 tile col (41x28), fill it with map data chunks from real map, aligned by x offset (0 at beginning, because start from left side).
2) fill vram with actual tiles, using indexes from map buffer.
3) set horizontal scroll for plane.
4) when plane scrolled by 8 pixels, so extra tile col became visble, i stop scrolling, and refill map buffer from map with x+1 offset, refill vram with new tiles.


It works. Barely. Very visible tearing/blinking, no smooth. I tried disable display and enable dma, when refilling new tiles - but it just make blinking even more visble. Overall process looks like it scrolled by tiles, not pixels. But if i enable wating for Vsync, i may see that initial scroll (before first 8 pixels limit) is very smooth and scrolled by single pixels. But afterward it start scrolling by tiles, not pixels..No smooth at all.



Here is code. Btw, I understand that its lame, and probably it must be done slightly different way. Would be happy if someone will give me hint on this.

P.S. Thanks to Stef for his great Genesis Devkit!
P.P.S. Yesterday spend all evening watching various demos on this great forum. Guys, its amazing what u do with Sega Genesis!

Code: Select all

	for (y = 0; y < 29; y++) {
		for (i = 0; i < 42; i++) map1_buff[y*41+i] = map1[y*230+i];	//fill 41x28 map buffer with data from picture map, offset x=0
	}
	for (i = 0; i < 1148; i++) {
		tile_ptr = &tile[map1_buff[i]*8];
		VDP_loadTileTo(tile_ptr,i,1,0);					//fill vram with tiles
	}

	VDP_fillTileRectInc(APLAN, TILE_ATTR(0,0,0,0),0,0,41,28);
	VDP_setPalette(0,palette0);

	for (;;) {

		if(BUTTON_RIGHT & joy) VDP_setHorizontalScroll(APLAN, 0, 0-scroll++);	//press right, to scroll pic from left to right
		
		if(scroll == 8) {							//when pic scrolled by 8px (1 tile col), reload new tiles to vram, offset x++
			scroll_tile_shift++;
			for (y = 0; y < 29; y++) {
				for (i = 0; i < 42; i++) map1_buff[y*41+i] = map1[scroll_tile_shift+y*230+i];
			}

			//VDP_setReg(0x01,0x14);					//just make blinks more visible

			for (i = 0; i < 1148; i++) {
				tile_ptr = &tile[map1_buff[i]*8];
				VDP_loadTileTo(tile_ptr,i,1,1);
			}

			//VDP_setReg(0x01,0x74);

			scroll = 0;
		
		}

		//VDP_waitVSync();
		//VDP_blit();
	}

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Fri Feb 01, 2013 4:27 am

You could double-buffer the name table - make it 128x32 and use the first 41 columns for the display until you scroll one full column, then switch the scroll to the last 41 columns, which you load before setting the scroll (set the scroll in the vertical blank). When that is scrolled one full column, switch the scroll back to 0, which you again set before setting the scroll.

No matter what, that would never show any tearing or blinking.

Gigasoft
Very interested
Posts: 95
Joined: Fri Jan 01, 2010 2:24 am

Post by Gigasoft » Fri Feb 01, 2013 11:22 am

You should only update the edges. Then you only have to load 112 bytes for every pixel scrolled.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Fri Feb 01, 2013 2:57 pm

This thread should be useful. I spent some time explaining how I did scrolling in Goplanes for oofwill.

viewtopic.php?t=1081&highlight=

peekpoke
Interested
Posts: 37
Joined: Fri Feb 01, 2013 1:11 am

Post by peekpoke » Fri Feb 01, 2013 7:36 pm

Chilly Willy, Gigasoft, djcouchycouch, thank you very much for answering!

Chilly Willy, tried to implement ur solution first. I set plan to 128x32 size, then "drawed" first 41x28 chunk of picture at left side of plan. Then started scrolling plan from left to right by 1px. When scrolled 8px, vblank callback "drawed" new 41x28 chunk ofsetted by 1 tile on right side and set scroll on this frame. Still no good :( When it swap sides, for a moment it looks like it displays prev frame for a second. No smooth at all. I attached full source. Pls, if its possible and not very difficult for u, may u take a look. Because got in the dead end now :(

In meantime, ill take a look at thread link provided by djcouchycouch (Ty!)

P.S. Just thinking. I want to scroll this big picture (230x28 tiles). I used ImaGenesis to convert it to optimized pal,tiles,map set, so it allowed to have as much as 4024 tiles instead of 6440. But 4024 is still too much for vram, and on same side, i also need cycle throught map indexes to drawl frames, loading tile by tile to vram. So, i do this for every tile on frame (41x28=1148 tiles):, cycle map, get tile index, upload this single tile to vram, cycle++ And it must be done durink vblank int.
Maybe this gives this noticeable overhead, whic causes no smooth scrolling? Maybe try to use not optimised tile/pal (6440) so, tiles will be copied continiously and linearly to vram, no need cycle indexes... But still, need to transfer all frame tiles (1148) during vblank - its possible at all?

About scrolling + updating only edges - i want to try this method next. But, cant understand mechanics yet - need to get little timeout vblank xD

Code: Select all

#include "genesis.h"
extern u32 tile[];
extern u16 map1[];
extern u16 palette0[];

#define PIC_WIDTH	230
//picture width in 8x8 tiles

#define FRAME_BUFFER_W	41
//frame width in pixels, 41 allow it to be scrolled left to right for 8 pixels (one tile)

u16 scroll = 0;		//scroll pixel offset
u16 scroll_tile = 0;	//scroll tile offset
u8 frame = 0;		//draw frame on left (0) or right (1) side of plan

u32 *tile_ptr;
u16 y = 0;
u16 i = 0;
u8 swap=0;		//"draw" frame and set scroll during vblank (0-no, 1-yes)

static void vblank();
static void drawframe();
static char str1[32];

int main() {
	VDP_init();
	VDP_setPlanSize(128, 32);
	setVBlankCallback(vblank);
	VDP_setPalette(0,palette0);
	drawframe();
	for (;;) {
		uintToStr(frame,str1,1);
		VDP_drawText(APLAN, "frame: ", 3<<13, 1, 2);
		VDP_drawText(APLAN, str1, 3<<13, 14, 2);
		uintToStr(scroll,str1,4);
		VDP_drawText(APLAN, "scroll: ", 3<<13, 1, 4);
		VDP_drawText(APLAN, str1, 3<<13, 14, 4);
		uintToStr(scroll_tile,str1,4);
		VDP_drawText(APLAN, "scroll_tile: ", 3<<13, 1, 6);
		VDP_drawText(APLAN, str1, 3<<13, 14, 6);
		u16 joy = JOY_readJoypad(JOY_1);
		if(BUTTON_RIGHT & joy && swap == 0) {
			VDP_setHorizontalScroll(BPLAN, 0, 0-scroll++);
			//VDP_waitVSync();
		}
		if(scroll==8) {
			scroll_tile++;
			frame = 1;
			scroll = (128 - FRAME_BUFFER_W)*8;
			swap = 1;
		}
		if(scroll==(8 + (128 - FRAME_BUFFER_W)*8)) {
			scroll_tile++;
			frame = 0;
			scroll = 0;
			swap = 1;
		}
	}
	return 0;
}
static void vblank() {
	if(swap) {
		drawframe();
		VDP_setHorizontalScroll(BPLAN, 0, -scroll);
		swap = 0;
	}
}
static void drawframe() {
	for (y = 0; y <= 28; y++) {
		for (i = 0; i <= FRAME_BUFFER_W; i++) {
			tile_ptr = &tile[map1[scroll_tile + y*PIC_WIDTH + i]*8];
			VDP_loadTileTo(tile_ptr,1 + y*FRAME_BUFFER_W + i,1,0);
			VDP_setTile(BPLAN, (1 + y*FRAME_BUFFER_W + i) | TILE_ATTR(0, 0, 0, 0), (128 - FRAME_BUFFER_W)*frame + i, y);
		}
	}
}

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Fri Feb 01, 2013 7:50 pm

You update the scroll in the move routine as well as in the vblank... which means that when you move, you have a good chance of setting the scroll in the middle of the display, giving bad results. You should ONLY update scroll in the vblank to avoid problems.

You also need to synch your main loop to the vblank or you could outpace the vblank update. Do your game logic, then wait for the vblank. That also means your game runs at a steady rate.

peekpoke
Interested
Posts: 37
Joined: Fri Feb 01, 2013 1:11 am

Post by peekpoke » Fri Feb 01, 2013 8:31 pm

Thank you very much!

Did those quick changes. Removed setting of horizontal scroll from main loop (at joy check - also now it not check if swap is set). Left there only scroll variable increment. Added VDP_waitVSync() in the end of main loop. Now each vblank horizontal scroll is setted up. And if swap==1, also draw frame.

Still no smoothness at swap - very visible blink, and visually see filliing tiles... update also, noticed, that during swap, picture somehow shifts to left (maybe on 1 tile width), but when swap finished, all looks on its own places. I.e. before swap to +1x tile frame, it shifts on +2x tiles :o But after swap finshed, got needed 1x shift. In simple words - it looks like screen jumps left for 1 extra column and then jump back :lol:

Maybe i choose wrong approach for this task? Maybe uploadng full frame at vblank bad idea? :(

Code: Select all

--- main.old	Fri Feb 01 22:50:22 2013
+++ main.c	Fri Feb 01 23:11:57 2013
@@ -39,9 +39,8 @@
       VDP_drawText(APLAN, "scroll_tile: ", 3<<13, 1, 6);
       VDP_drawText(APLAN, str1, 3<<13, 14, 6);
       u16 joy = JOY_readJoypad(JOY_1);
-      if(BUTTON_RIGHT & joy && swap == 0) {
-         VDP_setHorizontalScroll(BPLAN, 0, 0-scroll++);
-         VDP_waitVSync();
+      if(BUTTON_RIGHT & joy) {
+         scroll++;
       }
       if(scroll==8) {
          scroll_tile++;
@@ -55,15 +54,16 @@
          scroll = 0;
          swap = 1;
       }
+         VDP_waitVSync();
    }
    return 0;
 }
 static void vblank() {
    if(swap) {
       drawframe();
-      VDP_setHorizontalScroll(BPLAN, 0, -scroll);
       swap = 0;
    }
+   VDP_setHorizontalScroll(BPLAN, 0, -scroll);
 }
 static void drawframe() {
    for (y = 0; y <= 28; y++) {

TmEE co.(TM)
Very interested
Posts: 2440
Joined: Tue Dec 05, 2006 1:37 pm
Location: Estonia, Rapla City
Contact:

Post by TmEE co.(TM) » Sat Feb 02, 2013 12:43 am

There are only 2048 tiles total that fit in VRAM, and you wont get all of these...

You can only update about 230 tiles per VBL in 60Hz and about 550 in 50Hz
Mida sa loed ? Nagunii aru ei saa ;)
http://www.tmeeco.eu
Files of all broken links and images of mine are found here : http://www.tmeeco.eu/FileDen

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sat Feb 02, 2013 2:50 am

When you scroll by one tile, you aren't changing the tiles, only the name table. There should be plenty of time in the vblank to update the name table.

I think there's still a disconnect between the scroll code in your main loop and the vblank. If you look at your code, a vblank could occur anywhere in the middle of this block:

Code: Select all

       if(BUTTON_RIGHT & joy) {
          scroll++;
       }
       if(scroll==8) {
          scroll_tile++;
          scroll = 0;
          swap = 1;
       }
       VDP_waitVSync();
Depending on where it is in that code, any number of bad things can happen. For example, suppose the vblank occurs right as you set scroll to 0, but before swap is set to 1. Bad things. Since these variables are used from an interrupt, you need to protect them - disable the ints before they are updated, and enable the ints after they're all set. Make sure these variables are all defined as volatile as well or bad things can happen.

peekpoke
Interested
Posts: 37
Joined: Fri Feb 01, 2013 1:11 am

Post by peekpoke » Sun Feb 03, 2013 4:13 pm

Thank you, Chilly Willy - readed about volatile variables, also refreshed knowledge on clearing/setting bits, so may now propertly disable/enable vblank int, when needed.

But in meantime tried to implement "edge only" method, thx to djcouchycouch and Gigasoft for pointing on it.

I just load tiles into vram by columns, 41 column total. Then scroll by pixels, when offset = 8 px, increment column offset, then load next single column from picture array at vram+((column_offset-1)*41), and fill it on plane at column_offset + 41. So, every moment, there is only 41x28 tiles in vram, with new tiles overwriting scrolled out of screen old tiles.

It works very smoothly. But have few little bugs now - first, when 41 columns scrolled, i need to reset vram uploading offset, so new tiles again overwrite old tiles from start offset and not utilise more vram (41x28 tiles in vram). I did it, but cant catch some accumulated error - so every offset reset, i see new column appered at left (+1 column at left every reset).
Second bug - plane width 128, when it reach end, and so, it loop, some bugged row appear on top of screen (+1 row at each loop)


Btw, tried at same time create same scrolling for 30 colors, two plane, 2 palettes, picture. But sadly - only ~1400 tiles maybe placed in vram safely. So fullscreen (41x28*2planes = 2296 tiles) not possible for me using same method. At least got it workng only if use max 41x18 - but same bugs i explaned before appears 2x time more xD

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sun Feb 03, 2013 6:04 pm

peekpoke wrote:i need to reset vram uploading offset,
You just need to apply a modulo to your offset all the time. Since it's 64, you can use a "& 63" instead.

offset = offset & 63; // will never go over 63.

Gigasoft
Very interested
Posts: 95
Joined: Fri Jan 01, 2010 2:24 am

Post by Gigasoft » Sun Feb 03, 2013 7:54 pm

It should be possible to have two planes if you use 256x224 mode, but then you have to update the entire name table for every tile scrolled. Here's what you do: Put both name tables at E000h as 128x32. Plane A should start out at 0,32 and plane B at 264,32, and they will advance through 8 horizontal positions in a cycle. Reserve a 32 byte area for H scroll and another for the sprite table. Now, you have 1906 tiles available, which is more than enough.

Post Reply