Skip to content

Instantly share code, notes, and snippets.

@bc-bytes
Last active September 2, 2017 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bc-bytes/24567f328b2d8a3be5dad3a5071f62ee to your computer and use it in GitHub Desktop.
Save bc-bytes/24567f328b2d8a3be5dad3a5071f62ee to your computer and use it in GitHub Desktop.
SDL2 Stuttering Issue Example
/*This source code copyrighted by Lazy Foo' Productions (2004-2015)
and may not be redistributed without written permission.*/
//Using SDL, SDL_image, standard IO, and strings
#include <SDL.h>
#include <SDL_image.h>
#include <string>
#include <vector>
#include <sstream>
//Screen dimension constants
const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 800;
const int SCREEN_FPS = 60;
const int SCREEN_TICKS_PER_FRAME = 1000 / SCREEN_FPS;
//Texture wrapper class
class LTexture
{
public:
//Initializes variables
LTexture();
//Deallocates memory
~LTexture();
//Loads image at specified path
bool loadFromFile(std::string path);
#ifdef _SDL_TTF_H
//Creates image from font string
bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
#endif
//Deallocates texture
void free();
//Set color modulation
void setColor(Uint8 red, Uint8 green, Uint8 blue);
//Set blending
void setBlendMode(SDL_BlendMode blending);
//Set alpha modulation
void setAlpha(Uint8 alpha);
//Renders texture at given point
void render(int x, int y, SDL_Rect *clip = NULL, double angle = 0.0, SDL_Point *center = NULL,
SDL_RendererFlip flip = SDL_FLIP_NONE);
//Gets image dimensions
int getWidth();
int getHeight();
private:
//The actual hardware texture
SDL_Texture *mTexture;
//Image dimensions
int mWidth;
int mHeight;
};
//The application time based timer
class LTimer
{
public:
//Initializes variables
LTimer();
//The various clock actions
void start();
void stop();
void pause();
void unpause();
//Gets the timer's time
Uint32 getTicks();
//Checks the status of the timer
bool isStarted();
bool isPaused();
private:
//The clock time when the timer started
Uint32 mStartTicks;
//The ticks stored when the timer was paused
Uint32 mPausedTicks;
//The timer status
bool mPaused;
bool mStarted;
};
//The enemy that will move around on the screen
class Enemy
{
public:
static const int WIDTH = 53;
static const int HEIGHT = 68;
//Maximum axis velocity of the enemy
static const int VEL = 10;
short state;
//Initialise the variables
Enemy();
//Takes key presses and adjusts the dot's velocity
void handleEvent(SDL_Event &e);
void move();
void render();
private:
float mPosX, mPosY;
float mVelX, mVelY;
};
//Starts up SDL and creates window
bool init();
//Loads media
bool loadMedia();
//Frees media and shuts down SDL
void close();
//The window we'll be rendering to
SDL_Window *gWindow = NULL;
//The window renderer
SDL_Renderer *gRenderer = NULL;
//Scene textures
LTexture gEnemyTexture;
LTexture::LTexture()
{
//Initialize
mTexture = NULL;
mWidth = 0;
mHeight = 0;
}
LTexture::~LTexture()
{
//Deallocate
free();
}
bool LTexture::loadFromFile(std::string path)
{
//Get rid of preexisting texture
free();
//The final texture
SDL_Texture *newTexture = NULL;
//Load image at specified path
SDL_Surface *loadedSurface = IMG_Load(path.c_str());
if (loadedSurface == NULL)
{
printf("Unable to load image %s! SDL_image Error: %s\n", path.c_str(), IMG_GetError());
}
else
{
//Color key image
SDL_SetColorKey(loadedSurface, SDL_TRUE, SDL_MapRGB(loadedSurface->format, 0, 0xFF, 0xFF));
//Create texture from surface pixels
newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
if (newTexture == NULL)
{
printf("Unable to create texture from %s! SDL Error: %s\n", path.c_str(),
SDL_GetError());
}
else
{
//Get image dimensions
mWidth = loadedSurface->w;
mHeight = loadedSurface->h;
}
//Get rid of old loaded surface
SDL_FreeSurface(loadedSurface);
}
//Return success
mTexture = newTexture;
return mTexture != NULL;
}
void LTexture::free()
{
//Free texture if it exists
if (mTexture != NULL)
{
SDL_DestroyTexture(mTexture);
mTexture = NULL;
mWidth = 0;
mHeight = 0;
}
}
void LTexture::render(int x, int y, SDL_Rect *clip, double angle, SDL_Point *center,
SDL_RendererFlip flip)
{
//Set rendering space and render to screen
SDL_Rect renderQuad = {x, y, mWidth, mHeight};
//Set clip rendering dimensions
if (clip != NULL)
{
renderQuad.w = clip->w;
renderQuad.h = clip->h;
}
//Render to screen
SDL_RenderCopyEx(gRenderer, mTexture, clip, &renderQuad, angle, center, flip);
}
LTimer::LTimer()
{
//Initialize the variables
mStartTicks = 0;
mPausedTicks = 0;
mPaused = false;
mStarted = false;
}
void LTimer::start()
{
//Start the timer
mStarted = true;
//Unpause the timer
mPaused = false;
//Get the current clock time
mStartTicks = SDL_GetTicks();
mPausedTicks = 0;
}
void LTimer::stop()
{
//Stop the timer
mStarted = false;
//Unpause the timer
mPaused = false;
//Clear tick variables
mStartTicks = 0;
mPausedTicks = 0;
}
void LTimer::pause()
{
//If the timer is running and isn't already paused
if (mStarted && !mPaused)
{
//Pause the timer
mPaused = true;
//Calculate the paused ticks
mPausedTicks = SDL_GetTicks() - mStartTicks;
mStartTicks = 0;
}
}
Uint32 LTimer::getTicks()
{
//The actual timer time
Uint32 time = 0;
//If the timer is running
if (mStarted)
{
//If the timer is paused
if (mPaused)
{
//Return the number of ticks when the timer was paused
time = mPausedTicks;
}
else
{
//Return the current time minus the start time
time = SDL_GetTicks() - mStartTicks;
}
}
return time;
}
bool LTimer::isPaused()
{
//Timer is running and paused
return mPaused && mStarted;
}
Enemy::Enemy()
{
mPosX = 0;
mPosY = (rand() % (SCREEN_HEIGHT - Enemy::HEIGHT));
mVelX = 0.1f + static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / 0.6521f));
mVelY = 0;
state = 0; // 0 = left, 1 = right
//SDL_Log("mVelX:%f", mVelX);
}
Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
double deltaTime = 0;
void Enemy::move()
{
if (state == 0)
{
mPosX += mVelX * deltaTime; // right
if (mPosX > (SCREEN_WIDTH - Enemy::WIDTH))
{
state = 1;
}
}
else
{
mPosX -= mVelX * deltaTime; // left
if (mPosX < 1)
{
state = 0;
}
}
}
void Enemy::render()
{
gEnemyTexture.render(mPosX, mPosY);
}
bool init()
{
//Initialization flag
bool success = true;
//Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("SDL could not initialize! SDL Error: %s\n", SDL_GetError());
success = false;
}
else
{
//Set texture filtering to linear
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"))
{
printf("Warning: Linear texture filtering not enabled!");
}
//Create window
gWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (gWindow == NULL)
{
printf("Window could not be created! SDL Error: %s\n", SDL_GetError());
success = false;
}
else
{
//Create vsynced renderer for window
gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (gRenderer == NULL)
{
printf("Renderer could not be created! SDL Error: %s\n", SDL_GetError());
success = false;
}
else
{
//Initialize renderer color
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
//Initialize PNG loading
int imgFlags = IMG_INIT_PNG;
if (!(IMG_Init(imgFlags) & imgFlags))
{
printf("SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError());
success = false;
}
}
}
}
return success;
}
const int maxEnemies = 6;
std::vector<Enemy> enemies;
bool loadMedia()
{
//Loading success flag
bool success = true;
//Load dot texture
if (!gEnemyTexture.loadFromFile("26_motion/Enemy45.png"))
{
printf("Failed to load enemy texture!\n");
success = false;
}
for (int i = 0; i < maxEnemies; ++i)
{
Enemy enemy;
enemies.push_back(enemy);
//SDL_Log("enemy created: %d", i);
}
return success;
}
void close()
{
//Free loaded images
gEnemyTexture.free();
//Destroy window
SDL_DestroyRenderer(gRenderer);
SDL_DestroyWindow(gWindow);
gWindow = NULL;
gRenderer = NULL;
//Quit SDL subsystems
IMG_Quit();
SDL_Quit();
}
LTimer fpsTimer; //The frames per second timer
LTimer capTimer; //The frames per second cap timer
int main(int argc, char *args[])
{
//Start up SDL and create window
if (!init())
{
printf("Failed to initialize!\n");
}
else
{
//Load media
if (!loadMedia())
{
printf("Failed to load media!\n");
}
else
{
//Main loop flag
bool quit = false;
//Event handler
SDL_Event e;
//Start counting frames per second
int countedFrames = 0;
fpsTimer.start();
//While application is running
while (!quit)
{
LAST = NOW;
NOW = SDL_GetPerformanceCounter();
deltaTime = ((NOW - LAST) * 1000 / (double) SDL_GetPerformanceFrequency());
//SDL_Log("dt:%f", deltaTime);
//Start cap timer
capTimer.start();
//Handle events on queue
while (SDL_PollEvent(&e) != 0)
{
//User requests quit
if (e.type == SDL_QUIT)
{
quit = true;
}
//Handle input
//player.handleEvent( e );
}
for (auto &enemy: enemies)
{
enemy.move();
}
//Calculate and correct fps
float avgFPS = countedFrames / (fpsTimer.getTicks() / 1000.f);
if (avgFPS > 2000000)
{
avgFPS = 0;
}
//Clear screen
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(gRenderer);
//Render objects
for (auto &enemy: enemies)
{
enemy.render();
}
//Update screen
SDL_RenderPresent(gRenderer);
++countedFrames;
//If frame finished early
int frameTicks = capTimer.getTicks();
if (frameTicks < SCREEN_TICKS_PER_FRAME)
{
//Wait remaining time
SDL_Delay(SCREEN_TICKS_PER_FRAME - frameTicks);
}
}
}
}
//Free resources and close SDL
close();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment