Last active
September 2, 2017 10:18
-
-
Save bc-bytes/24567f328b2d8a3be5dad3a5071f62ee to your computer and use it in GitHub Desktop.
SDL2 Stuttering Issue Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*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