I am trying to create a dynamic plotter in SDL2 for an embedded project. Currently, the code executes on both architectures, x86, and ARM. On x86 I get a smooth running plotter, however, on ARM it runs very slow at a fraction of the frames I get on x86. I am pretty sure this is because I rerender every pixel on the surface at this is a massive overheat on the embedded device.
I tried rendering the new content to a texture, copy it to the renderer and then render, but this did not work at all.
Due to the double-buffering, I have to clear every frame. Otherwise, I will "drop" changes. But I also need to render the old data points and only overwrite them when the plotter reaches them again.
Is there a way in SDL2 to save these data points to some sort of canvas and only add (redraw) the newly added ones?
Heres my source code:
Plotter.cpp
#include <SDL2/SDL.h>
#include <stdio.h>
#include "PlotterHelper.h"
/*Implementation*/
int main(int arhc, char * argv[])
{
//Top and bottom viewport
SDL_Rect topViewport;
SDL_Rect bottomViewport;
topViewport = CreateViewPort(0,0, SCREEN_WIDTH, (SCREEN_HEIGHT/2));
bottomViewport = CreateViewPort(0,(SCREEN_HEIGHT/2), SCREEN_WIDTH, SCREEN_HEIGHT/2);
float timeFrame = 4.0;
int updateWidth = round(SCREEN_WIDTH/(60.0 * timeFrame));
uint8_t backgroundColor = 0xff;
int delayTime = 0;
int sinusScale = 0;
int rectWidth = RECT_WIDTH;
bool rectEnd = false;
SDL_Point points[SCREEN_WIDTH] = {0,0};
int pointPosition = 0;
if (!init())
{
printf("Init failed!\n");
}
else
{
SDL_ShowCursor(SDL_DISABLE);
//Main lopp flag
bool quit = false;
//Event handler
SDL_Event event;
//While application is running
while(!quit)
{
//Handle events on queue
while (SDL_PollEvent( &event) != 0)
{
//User requests quit
if(event.type == SDL_QUIT)
{
quit = true;
}
else if(event.type == SDL_KEYDOWN)
{
switch(event.key.keysym.sym)
{
case SDLK_w:
delayTime += 50;
if(delayTime > 5000)
delayTime = 5000;
break;
case SDLK_s:
delayTime -= 50;
if(delayTime < 0)
delayTime = 0;
break;
case SDLK_d:
timeFrame -= 1;
if(timeFrame < 1)
timeFrame = 1.0;
updateWidth = round(SCREEN_WIDTH/(60.0 * timeFrame));
printf("timeFrame = %lf\n", timeFrame);
break;
case SDLK_a:
timeFrame += 1;
if(timeFrame > 44)
timeFrame = 44.0;
updateWidth = round(SCREEN_WIDTH/(60.0 * timeFrame));
printf("timeFrame = %lf\n", timeFrame);
break;
case SDLK_r:
if(backgroundColor == 0x3f)
break;
else
{
++backgroundColor;
break;
}
case SDLK_f:
if(backgroundColor == 0x00)
break;
else
{
--backgroundColor;
break;
}
}
}
}
//Reset Plotter when the end of the window was reached
if(pointPosition > SCREEN_WIDTH-1)
{
pointPosition = 0;
sinusScale = (rand() % 100 + 1) - 50;
rectWidth = RECT_WIDTH;
rectEnd = false;
}
//Handler eraser when he reaches end of window
if(((SCREEN_WIDTH-1) - pointPosition) < RECT_WIDTH)
{
rectWidth = (SCREEN_WIDTH -1) - pointPosition;
rectEnd = true;
}
//Clear screen
SDL_SetRenderDrawColor(gRenderer, backgroundColor, backgroundColor, backgroundColor, 0xff);
SDL_RenderClear(gRenderer);
//Draw top viewport
SDL_RenderSetViewport( gRenderer, &topViewport );
SDL_SetRenderDrawColor(gRenderer, 0x00, 0x66, 0x00, 0xff);
for(int iterator = 0; iterator <= SCREEN_WIDTH -1; ++iterator)
{
SDL_RenderDrawLine(
gRenderer,
points[iterator].x,
0,
points[iterator].x,
SCREEN_HEIGHT/2);
}
PlotEraser(rectEnd, backgroundColor, rectWidth,points[pointPosition].x );
//raw bottom viewport
SDL_RenderSetViewport( gRenderer, &bottomViewport );
SDL_SetRenderDrawColor(gRenderer, 0x00, 0x66, 0x00, 0xff);
for(int iterator = 0; iterator <= SCREEN_WIDTH -1; ++iterator)
{
SDL_RenderDrawLine(
gRenderer,
points[iterator].x,
SCREEN_HEIGHT/2,
points[iterator].x,
points[iterator].y);
}
PlotEraser(rectEnd, backgroundColor, rectWidth,points[pointPosition].x );
for(int iterator = pointPosition; iterator <= pointPosition + updateWidth; ++iterator)
{
points[iterator].x = iterator;
points[iterator].y = round(((SCREEN_HEIGHT/4 )* sin(iterator/(100.0+sinusScale))) + SCREEN_HEIGHT/4);
}
pointPosition += updateWidth;
//Update Screen
SDL_RenderPresent(gRenderer);
SDL_Delay(delayTime);
}
}
//Free resources and close SDL
close();
return 0;
}
/*End of File*/
PlotterHelper.cpp
/*Includes*/
#include "PlotterHelper.h"
SDL_Window * gWindow = NULL;
SDL_Renderer * gRenderer = NULL;
SDL_Renderer * intermediate = NULL;
/*Implementation*/
/*********************************************************************
*********************************************************************
*********************************************************************
*/
bool init()
{
//Init flag
bool success = true;
//Init 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!" );
}
//Set VSYNC
if( !SDL_SetHint( SDL_HINT_RENDER_VSYNC, "1" ) )
{
printf( "Warning: VSYNC not enabled!" );
}
//Create window
gWindow = SDL_CreateWindow(
"SDL Plotter",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH,
SCREEN_HEIGHT,
SDL_WINDOW_SHOWN );
if (NULL == gWindow)
{
printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//Create renderer for window
gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED );
if(NULL == gRenderer)
{
printf("Renderer could not be created! SDLError: %s\n", SDL_GetError());
success = false;
}
else
{
//Initialise renderer colour
SDL_SetRenderDrawColor(gRenderer, 0xff, 0xff, 0xff, 0xff);
}
}
}
return success;
}
/*********************************************************************
*********************************************************************
*********************************************************************
*/
void close()
{
//Destroy window
SDL_DestroyRenderer(gRenderer);
SDL_DestroyWindow(gWindow);
gRenderer = NULL;
gWindow = NULL;
//Quit SDL subsystems
SDL_Quit();
}
/*********************************************************************
*********************************************************************
*********************************************************************
*/
SDL_Rect CreateViewPort(int x, int y, int w, int h)
{
SDL_Rect Viewport;
Viewport.x = x;
Viewport.y = y;
Viewport.w = w;
Viewport.h = h;
return Viewport;
}
/*********************************************************************
*********************************************************************
*********************************************************************
*/
void PlotEraser(bool rectEnd, uint8_t backgroundColor, int rectWidth, int x)
{
SDL_Rect fillRectBot = {x, 0, rectWidth, SCREEN_HEIGHT/2};
SDL_SetRenderDrawColor(gRenderer, backgroundColor, backgroundColor, backgroundColor, 0xff);
SDL_RenderFillRect(gRenderer, &fillRectBot);
if(rectEnd)
{
int startRecWidth = RECT_WIDTH - rectWidth;
SDL_Rect startRec = {0, 0, startRecWidth, SCREEN_HEIGHT/2};
SDL_RenderFillRect(gRenderer, &startRec);
}
}
/*End of File*/
PlotterHelper.h
#ifndef PLOTTER_HELPER_H_
#define PLOTTER_HELPER_H_
#include <SDL2/SDL.h>
#include <stdio.h>
#include <cmath>
#include <string>
/*Globals*/
//Screen constants
const int SCREEN_WIDTH = 1024;
const int SCREEN_HEIGHT = 768;
const int RECT_WIDTH = 10;
//The window we'll be rendering to
extern SDL_Window * gWindow;
//The window renderer
extern SDL_Renderer * gRenderer;
extern SDL_Renderer * intermediate;
/*Prototypes*/
/*********************************************************************
*********************************************************************
*********************************************************************
*/
//Starts up SDL and creates window
bool init();
/*********************************************************************
*********************************************************************
*********************************************************************
*/
//Free media and shut down SDL
void close();
/*********************************************************************
*********************************************************************
*********************************************************************
*/
//CreateVieport
SDL_Rect CreateViewPort(int x, int y, int w, int h);
/*********************************************************************
*********************************************************************
*********************************************************************
*/
//Plot Eraser
void PlotEraser(bool rectEnd, uint8_t backgroundColor, int rectWidth, int x);
#endif /* PLOTTER_HELPER_H_ */
/*End of File*/
You probably have a couple of options that can help:
You ought to be able to accomplish this using a "render target texture". That is an SDL_Texture
you create (see SDL_CreateTexture
) with the SDL_TEXTUREACCESS_TARGET
flag.
You would draw new points to this texture by calling SDL_SetRenderTarget
and passing this texture before then rendering the points.
your main loop would then need to call SDL_SetRenderTarget
and pass nullptr
to restore the window as the rendering target. You then render your texture to the window every frame.
The SDL documentation has a small sample demoing how to use a render target texture.
the latest version of SDL 2 supports render batching if you set SDL_HINT_RENDER_BATCHING
(SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1");
). You should use SDL_CreateWindowAndRenderer()
to ensure you have batching enabled.
This may not do anything if your platform doesnt support hardware acceleration. You can use this in conjunction with a render target. read about SDL render batching here.
This is separate from render batching and should work on any version of SDL 2.
If you can, collect all your rectangles/points together in an array you can use
SDL_RenderDrawPoints
or SDL_RenderDrawRects
to have SDL do them all at once which should improve performance even if you have no hardware acceleration. This can be used in conjunction with a render target.
If your device lacks hardware acceleration, then it might be faster to ditch SDL_Renderer and instead use SDL_GetWindowSurface
to get the SDL_Surface
for the window and use SDL_BlitSurface
(or manually set pixels via surface->pixels
) to draw directly to that then update the window with SDL_UpdateWindowSurface
. See also SDL_UpdateWindowSurfaceRects
for how to only update the rectangles you are changing for even better performance.
You will need to familiarize yourself with SDL_Surface
as you will need to inspect the pixel format of the window surface in order to update it correctly should you choose to directly manipulate the pixels.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With