Jump to content


Photo

Sdl Smooth Scrolling Tips?


  • Please log in to reply
36 replies to this topic

#16 Miner49er

Miner49er

    GP Mania

  • GP32 Hardcore
  • PipPipPipPipPip
  • 403 posts
  • Gender:Male

Posted 14 October 2009 - 11:05 AM





I believe that if you could post your main loop's code that would allow us to help you more.


	while(!exitProgram)
	{
		fps.start();

                if(menu->screenMode == PlayingGame) 
			gameLoop();//this checks input and animates one frame
		else
			menuLoop();//this checks input and animates one frame

		if(menu->screenMode == PlayingGame) 
			game->Draw(screen);
		else 	
		 	menu->Draw(screen);
		
		SDL_Flip(screen);

		diff = (1000 / updateFPS) - fps.GetTicks();
		if( diff > 0 )
			SDL_Delay( diff );
	}


Edited: Because I pasted a load of crap by accident. fps class just records the number of ticks (GetTicks return the time passed since Start)


I don't see any problems here. Probably the frames take too long to draw. Did you check how much time does it take to draw one frame?

You may also check if you haven't got any float/int implicit conversions taking place. It's a longshot but it's easy to miss and causes strange errors.

Also, how do you store coordinates? They shouldn't be ints, the conversion to int should be just before drawing. Rounding can cause laggy movement.


Hmmm, well as I explained before all menuLoop bit does is blit a background image and display about 50 16x16 images. So it's not doing a great deal.
Thing is, I seem to recall doing a scrolling message using AMOS basic about 20 years ago and getting PERFECT scrolling, no jitter, no worries. Now, I did have a fixed delay then as my brain hadn't figured out the variable-delay-dependant-on-draw-time bit but still... Of course my Amiga would've just had the AMOS process running, so no background threads/processes to cause jitter. This, I think, is the problem I'm encountering.

Also, the co-ords as ints comment: I'm using a 1 to 1 co-ord to pixel ratio, so I don't understand. I guess I could scale the scroller position up, move a fraction then scale down to draw...but I'm scrolling it along 1 pixel at a time right now, so I dont see the benefit.


If the time needed to draw the frame is low as you say then I highly doubt that any applications running in the background could cause that (if you're not running movie encoding or something very, very resource intensive). The priority and scheduling tricks are reserved for extremely low latency applications which do something hundreds of thousands times a second or so - that's when the system scheduler can cause jitter, not in your case. I had to use this tricks when I was doing RGB LED PWM modulation by LPT port. The modulation pulses where generated by the CPU...

Just check your CPU load graph.


Running here at work (on windows Vista, ughh), it's using 3% CPU on a 3GigHz machine! It's not running too fast for the Delay but STILL it jitters. I reckon this can only mean that the SDL_GetTicks() isn't accurate enough. Is it worth trying a higher resolution timer?

#17 Unfathomable Depths

Unfathomable Depths

    sláinte

  • GP32 Hardcore
  • PipPipPipPipPipPip
  • 877 posts
  • Gender:Male
  • Location:Scotland

Posted 14 October 2009 - 11:09 AM

There is no way that would tax a reasonably modern PC.

On the GP2X SDL_Flip waits for the vertical blank. I'm not sure it does on Windows. No idea about Linux.
In saying that I've never had a problem on Windows with jittering or tearing.

The only other thing I can think of trying is to use SDL_HWSURFACE if you're not already.

The only thing I do different from your example is I call SDL_Flip at the very end of the loop. That wouldn't make any difference though, I don't think.

*edit* No I don't. Here's my wait loop:
SDL_Flip(screen);
		
	    //Cap the frame rate
        while( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay(1);
        }

Edited by Unfathomable Depths, 14 October 2009 - 11:23 AM.


#18 Miner49er

Miner49er

    GP Mania

  • GP32 Hardcore
  • PipPipPipPipPip
  • 403 posts
  • Gender:Male

Posted 14 October 2009 - 11:36 AM

There is no way that would tax a reasonably modern PC.

On the GP2X SDL_Flip waits for the vertical blank. I'm not sure it does on Windows. No idea about Linux.
In saying that I've never had a problem on Windows with jittering or tearing.

The only other thing I can think of trying is to use SDL_HWSURFACE if you're not already.

The only thing I do different from your example is I call SDL_Flip at the very end of the loop. That wouldn't make any difference though, I don't think.

*edit* No I don't. Here's my wait loop:

SDL_Flip(screen);
		
	    //Cap the frame rate
        while( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
        {
            SDL_Delay(1);
        }


Well, My collegue here at work thinks that It looks fine. I know it doesn't look 100% but maybe I should move on for now. perhaps I can get some feedback once I've finished.

#19 Rockthesmurf

Rockthesmurf

    GP Mania

  • GP32 Hardcore
  • PipPipPipPipPip
  • 314 posts
  • Gender:Male
  • Location:Manchester, UK

Posted 14 October 2009 - 12:20 PM

Something like this is a common approach:

while ( application_running )
{
RenderEverythingFromLastFrame( );

UpdateAllMyGameObjects( );

BuildUpEverythingThatNeedsRenderingThisFrame( );

WaitForCurrentRenderToFinish( );

OptionallyWaitForVSync( );
}

The idea being you start the render for the previous frame straight away, if you are using a graphics API that blocks then it would need to be done on a seperate render thread (if you are using one that doesn't block and just internally buffers the commands this isn't neccessarily). While the previous frame is rendering you can do all your game logic, and build up the next frames render calls - obviously you have to make sure anything that gets used by the render is double buffered (so you aren't changing values of data currently being drawn). Once you have done everything you need to, you just wait for the draw to finish, vsync if desired, and then start again (flipping any double buffered stuff you are using). With this approach the graphics/processing can run in parallel with each other, with a loop more like:

while ( application_running )
{
UpdateAllMyGameObjects( );

BuildUpEverythingThatNeedsRenderingThisFrame( );

RenderEverythingForThisFrame( );

WaitForCurrentRenderToFinish( );

OptionallyWaitForVSync( );
}

Your render can't even start until all your processing is done, then once you start rendering you have to wait for it to finish before you can start the next frames processing.

Maybe this doesn't help you so much, but it is what I'd do anyway!

Steve

#20 skeezix

skeezix

    Mega GP Mania

  • GP Guru
  • 5088 posts
  • Gender:Male
  • Interests:Blog: http://www.rjmitchell.ca/~jeff/blog2009/

Posted 14 October 2009 - 01:53 PM

And the base of it it all is.. from your description and window size, there is no way it should be too much load for anything :) On a 100mhz GP32 I was trendering a full screen background and 50 or 100 little crappy sprites without slowing it down much (20fps or so) .. There must be somethign very unexpected going on for it to slow down any modern machine.

(ie: Like all your artwork is being scaled and rotated and colour-shifted every render, or you have an audio thread kicking in and consumingall your cpu or something :) Are you playing mp3s in background?

jeff

#21 Miner49er

Miner49er

    GP Mania

  • GP32 Hardcore
  • PipPipPipPipPip
  • 403 posts
  • Gender:Male

Posted 14 October 2009 - 02:57 PM

And the base of it it all is.. from your description and window size, there is no way it should be too much load for anything :) On a 100mhz GP32 I was trendering a full screen background and 50 or 100 little crappy sprites without slowing it down much (20fps or so) .. There must be somethign very unexpected going on for it to slow down any modern machine.

(ie: Like all your artwork is being scaled and rotated and colour-shifted every render, or you have an audio thread kicking in and consumingall your cpu or something :) Are you playing mp3s in background?

jeff


The slowdown is ONLY visible on horizontal scrolling (I only have horizontal...) and that is barely visible. I think I may have just become every so slightly obsessed with having things perfect.

I am, as it happens, scaling the entire image before flipping it (some monitors don't like 320x240, so I added a 2xScale( this has barely any effect on CPU usage. I switch scaling off and still notice jitter. Like I said though, my workmate reckons it looks absolutly fine so perhaps I'm jsut obsessing too much (I don't think I am obsessing TOO much - I mean, I want this to look lovely, not half-arsed)

#22 dflemstr

dflemstr

    It's a ball.

  • GP32 Hardcore
  • PipPipPipPipPipPip
  • 2249 posts
  • Gender:Male
  • Location:Stockholm, Sweden

Posted 14 October 2009 - 03:04 PM

Try this:
#define FRAME_AVG_COUNT 16
#define WANTED_FPS 60.0f

        float frameDeltas[FRAME_AVG_COUNT];
        const float wantedDelta = 1f / WANTED_FPS;
        int frameIdx = 0;
        int lastTicks = SDL_GetTicks();
        int i;
        
        for(i = 0; i < FRAME_AVG_COUNT; i++)
                frameDeltas[frameIdx] = wantedDelta;

        while(!exitProgram)
        {
                int newTicks = SDL_GetTicks();
                float delta = (float)(newTicks - oldTicks) / 1000f;
                oldTicks = newTicks;

                frameDeltas[frameIdx] = delta;
                frameIdx = (frameIdx + 1) % FRAME_AVG_COUNT;
                delta = 0;
                
                for(i = 0; i < FRAME_AVG_COUNT; i++)
                        delta += frameDeltas[frameIdx];
                
                delta /= (float)FRAME_AVG_COUNT;
                
                //Now use delta to scale all actions (don't rely on anything else)

                if(menu->screenMode == PlayingGame)
                {
                        gameLoop(delta);//this checks input and animates one frame
                        game->Draw(screen);

                        //example:
                        myObject->x += 3 * delta; //moves 3 pixels per second; x has to be float too btw
                }
                else
                {
                        menuLoop(delta);//this checks input and animates one frame
                        menu->Draw(screen);
                }
                
                SDL_Flip(screen);
                
                if(delta < wantedDelta)
                        SDL_Delay((int)((delta - wantedDelta) * 1000f));
        }

I may have made a few mistakes since I don't have gcc available but you get the general idea. Use multiple smoothing systems on top of one another and floats for everything, and you can't fail.

#23 Miner49er

Miner49er

    GP Mania

  • GP32 Hardcore
  • PipPipPipPipPip
  • 403 posts
  • Gender:Male

Posted 14 October 2009 - 04:04 PM

Try this:

#define FRAME_AVG_COUNT 16
#define WANTED_FPS 60.0f

        float frameDeltas[FRAME_AVG_COUNT];
        const float wantedDelta = 1f / WANTED_FPS;
        int frameIdx = 0;
        int lastTicks = SDL_GetTicks();
        int i;
        
        for(i = 0; i < FRAME_AVG_COUNT; i++)
                frameDeltas[frameIdx] = wantedDelta;

        while(!exitProgram)
        {
                int newTicks = SDL_GetTicks();
                float delta = (float)(newTicks - oldTicks) / 1000f;
                oldTicks = newTicks;

                frameDeltas[frameIdx] = delta;
                frameIdx = (frameIdx + 1) % FRAME_AVG_COUNT;
                delta = 0;
                
                for(i = 0; i < FRAME_AVG_COUNT; i++)
                        delta += frameDeltas[frameIdx];
                
                delta /= (float)FRAME_AVG_COUNT;
                
                //Now use delta to scale all actions (don't rely on anything else)

                if(menu->screenMode == PlayingGame)
                {
                        gameLoop(delta);//this checks input and animates one frame
                        game->Draw(screen);

                        //example:
                        myObject->x += 3 * delta; //moves 3 pixels per second; x has to be float too btw
                }
                else
                {
                        menuLoop(delta);//this checks input and animates one frame
                        menu->Draw(screen);
                }
                
                SDL_Flip(screen);
                
                if(delta < wantedDelta)
                        SDL_Delay((int)((delta - wantedDelta) * 1000f));
        }

I may have made a few mistakes since I don't have gcc available but you get the general idea. Use multiple smoothing systems on top of one another and floats for everything, and you can't fail.


Okay, so you're actually altering the amount an object moves by depending on the average time taken to draw stuff?
what if you want to move one pixel at a time? I shall look into this tonight, thanks :-)

Edit: Although, I was always tought to use ints and scale up/down to avoid using costly floats. Does the Pandora CPU have a Maths unit that allows floats? I like not having a maths unit - more challenging!

Edited by Miner49er, 14 October 2009 - 04:11 PM.


#24 dflemstr

dflemstr

    It's a ball.

  • GP32 Hardcore
  • PipPipPipPipPipPip
  • 2249 posts
  • Gender:Male
  • Location:Stockholm, Sweden

Posted 14 October 2009 - 04:57 PM

Edit: Although, I was always tought to use ints and scale up/down to avoid using costly floats. Does the Pandora CPU have a Maths unit that allows floats? I like not having a maths unit - more challenging!

It does not have a FPU in the core IIRC, but I think that you get access to floats anyways through the DSP or some related chip, and I was told that the float performance you get out of using it is only slightly worse compared to i686.

If that's not the case, then just replace all floats by fixed point values or even ints with manual scaling; I don't care as long as you have the ability to store fractional values somehow. I still believe that your jump issues are caused by an inaccurate timer and stacking rounding errors, and using averages and fractions respectively are the only solutions to those problems that I'm aware of.

#25 calc84maniac

calc84maniac

    GP32 Hardcore

  • GP32 Hardcore
  • PipPipPipPip
  • 211 posts

Posted 14 October 2009 - 06:02 PM


I believe that if you could post your main loop's code that would allow us to help you more.


	while(!exitProgram)
	{
		fps.start();

                if(menu->screenMode == PlayingGame) 
			gameLoop();//this checks input and animates one frame
		else
			menuLoop();//this checks input and animates one frame

		if(menu->screenMode == PlayingGame) 
			game->Draw(screen);
		else 	
		 	menu->Draw(screen);
		
		SDL_Flip(screen);

		diff = (1000 / updateFPS) - fps.GetTicks();
		if( diff > 0 )
			SDL_Delay( diff );
	}


Edited: Because I pasted a load of crap by accident. fps class just records the number of ticks (GetTicks return the time passed since Start)

What if you move the SDL_Flip call after the delay? Then it should update the screen at a regular rate, rather than drawing to the buffer at a regular rate (unless I'm missing something)

#26 Samurai_Crow

Samurai_Crow

    GP32 User

  • Members
  • PipPipPip
  • 84 posts
  • Interests:Amiga-related stuff.

Posted 14 October 2009 - 07:05 PM

http://olofson.net/examples.html has some SDL examples that use OpenGL for sub-pixel-precision rendering and parallax scrolling. You'd have to update them to use OpenGL-ES but it should get you started.

#27 skeezix

skeezix

    Mega GP Mania

  • GP Guru
  • 5088 posts
  • Gender:Male
  • Interests:Blog: http://www.rjmitchell.ca/~jeff/blog2009/

Posted 14 October 2009 - 07:59 PM

And watch for the classic blunder --

Once you start factoring delay in, you can easily screw up. Old FPSs in the Quake era famously had this issue -- based on lag, you woudl move the objects. So if you're a few frames behind, you move more, so that you have the same effective speed. Great.. until you run into 1fps situation, and suddenly your guy is jumping 60 frames worth in one move .. and if you don't calculate every possibly ollision for each of those steps that were not taken, you end up teleporting through walls. (ie: most ofthe time you calculate collisions where y are, and where you wish to enter .. but you have to b careful to allow for all possible inbetween as well, especially when youi potentially skip around due to delay lag..)

jeff

#28 Exophase

Exophase

    Exophase is bad. Nothing good will ever come of him.

  • GP Guru
  • 5463 posts
  • Location:Cleveland OH

Posted 14 October 2009 - 08:36 PM

It does not have a FPU in the core IIRC, but I think that you get access to floats anyways through the DSP or some related chip, and I was told that the float performance you get out of using it is only slightly worse compared to i686.

If that's not the case, then just replace all floats by fixed point values or even ints with manual scaling; I don't care as long as you have the ability to store fractional values somehow. I still believe that your jump issues are caused by an inaccurate timer and stacking rounding errors, and using averages and fractions respectively are the only solutions to those problems that I'm aware of.


No...

Cortex-A8 on Pandora has an FPU called VFPlite, but it's not pipelined so most useful operations take a minimum of several cycles, somewhere around 7. That's throughput, not just latency. VFPlite can handle both single and double precision and can conform to IEEE754 standards for formats and operations.

It also has a vector FPU called NEON, that can start a two-wide single precision operation per cycle. It's not IEEE754 compliant, and the compiler right now won't generate scalar code (mis)using it, as far as I know. It can vectorize code to use it, but not everything is vectorizable and I doubt GCC is that amazing at it anyway.

The DSP is purely fixed point.

I'm certain his problem has nothing to do with accumulated roundoff error from moving things, because he's moving at one pixel per interval. Making a 2D game time based instead of frame based is a waste if you can guarantee that the framerate will be acceptably high. Consoles have had fixed framerate and frame based timing for years. Since 2D doesn't have that dynamic a load it's not that hard to do it, and if it really does become a problem you can use frameskip to alleviate it, which will have similar results. The main thing is that you want to time to vsync and you want to have a consistent framerate as much as possible.

Anyway, here's what I think.. yes, SDL_Delay is not going to have perfect precision, probably no better than 1Hz, and it errs liberally. Even regardless of the precision you can end up being late. If SDL_Flip is also waiting for vsync (depends on your setup, but this is at least what you want it to be doing - and I'm going to guess it is because you said you're using double buffering) then your delay being late by even the most minute amount will cause it to miss the vsync you wanted to wait for, and SDL_Flip will now be waiting for the next one. Meaning an entire frame will be missed and it'll look jumpy.

The first thing you want to do is verify the problem. Keep a running average of frames per second by using SDL_GetTicks and a frames displayed counter. Run it for a long time. If it doesn't stabilize towards the fps you wanted then you know it's busted and it's not just mid-frame jitter. Now, to fix it, what you're going to want to do is delay for a lower period. You might have to do experiment to find out how much you can get away with. In fact, you can make a routine which does this at startup - try several delays back to back in tight loops and see how off you end up in the end by looking at the ticks. You can use this to find timer precision and overhead. You want to take that precision and round down towards it and then add the overhead. The goal will be to wait for as much as you feel safe waiting for, then let SDL wait for vsync for the rest. This is only necessary because I'm assuming SDL is spin locking to wait for vsync. I say this because I don't think that there are good generic ways of having the OS do it for its Windows or Linux targets. If I'm wrong then that means you should take out the delay entirely. In fact, you may want to try that first just to see if it fixes things.

Pandora won't have this problem because it will have a way to wait for vsync by using Linux's event system. Linux will be free to schedule other things in the mean time or halt the CPU, but once the vsync interrupt triggers the device driver waiting for it will wake up your process, which will probably be scheduled immediately thereafter.

#29 Kramy

Kramy

    Mega GP Mania

  • GP32 Hardcore
  • PipPipPipPipPipPip
  • 622 posts

Posted 15 October 2009 - 01:29 AM

Didn't read the whole thread.

You're describing Vsync. There's a "tear" somewhere across the screen whenever it jumps. Why you think it's jumping, is because part of the screen(say the top part) is displaying frame #32, and the bottom part is displaying frame #33. It makes it look like it jumped a frame, but really it's just vsync being off and timings not matching your monitor.

Vsync is pretty much the only way to match up. No timer will sync with an LCD.

#30 Exophase

Exophase

    Exophase is bad. Nothing good will ever come of him.

  • GP Guru
  • 5463 posts
  • Location:Cleveland OH

Posted 15 October 2009 - 03:04 AM

Didn't read the whole thread.

You're describing Vsync. There's a "tear" somewhere across the screen whenever it jumps. Why you think it's jumping, is because part of the screen(say the top part) is displaying frame #32, and the bottom part is displaying frame #33. It makes it look like it jumped a frame, but really it's just vsync being off and timings not matching your monitor.

Vsync is pretty much the only way to match up. No timer will sync with an LCD.


No he isn't. Read the rest of the thread ;P